JavaScript核心语法

Web开发专题 · 掌握Web前端交互的编程语言

专题:Web前端开发核心技术

关键词:JavaScript, ES6, Promise, 闭包, 箭头函数, async/await, 前端编程

一、JavaScript概述

1.1 什么是JavaScript

JavaScript是Web前端最核心的编程语言,与HTML和CSS并列为Web三大核心技术。HTML负责页面结构,CSS负责样式呈现,而JavaScript负责实现交互逻辑和动态行为。JavaScript是一种轻量级、解释型、面向对象的脚本语言,支持函数式编程和面向对象编程范式。

1995年由Brendan Eich在Netscape公司创建,仅用10天完成了第一个版本。随后经历多年标准化,ECMAScript(简称ES)成为JavaScript的语言规范标准。其中最重要的里程碑是2015年发布的ES6(ECMAScript 2015),它带来了大量现代化语言特性,彻底改变了JavaScript的编程方式。

1.2 ES6+ 重要更新

ES6及后续版本引入了一系列重大新特性,使得JavaScript代码更加简洁、可读性更强:

1.3 运行环境

JavaScript最初只在浏览器中运行,如今已经扩展到多个环境:

1.4 script标签引入方式

在HTML中引入JavaScript有三种方式:

<!-- 方式一:内联脚本(直接在HTML中写代码) --> <script> console.log('Hello, World!'); </script> <!-- 方式二:外部脚本文件(推荐) --> <script src="js/app.js"></script> <!-- 方式三:使用defer或async属性控制加载行为 --> <script src="js/app.js" defer></script> <script src="js/analytics.js" async></script>

defer属性:HTML解析完毕后按顺序执行脚本,多个defer脚本保持加载顺序。async属性:脚本下载完成后立即执行,不保证执行顺序,适用于独立工具脚本。defer通常优于async,因为它不影响DOM解析顺序。

二、变量与数据类型

2.1 var、let、const 的区别

JavaScript中有三种声明变量的方式,理解它们的区别是掌握JS的基础:

特性 var let const
作用域 函数作用域 块级作用域 块级作用域
变量提升 会提升(初始值为undefined) 会提升(暂时性死区) 会提升(暂时性死区)
重复声明 允许 不允许 不允许
重新赋值 允许 允许 不允许(对象属性可改)
挂载到window
// var — 函数作用域,存在变量提升 var a = 1; if (true) { var a = 2; // 同一个变量 } console.log(a); // 2 // let — 块级作用域,不存在重复声明 let b = 1; if (true) { let b = 2; // 不同变量 } console.log(b); // 1 // const — 常量,声明时必须赋值 const PI = 3.14159; // PI = 3; // TypeError: Assignment to constant variable const obj = { name: 'Alice' }; obj.name = 'Bob'; // 允许,对象本身可修改 // obj = {}; // TypeError

2.2 基本类型(Primitive Types)

JavaScript有7种基本数据类型:

2.3 引用类型(Reference Types)

引用类型包括Object、Array、Function等。与基本类型的值传递不同,引用类型通过引用传递:

// 基本类型 — 值传递 let x = 10; let y = x; y = 20; console.log(x); // 10 — x不受影响 // 引用类型 — 引用传递 let arr1 = [1, 2, 3]; let arr2 = arr1; arr2.push(4); console.log(arr1); // [1, 2, 3, 4] — arr1也被修改

2.4 typeof运算符与类型转换

typeof用于检测变量类型:

typeof 42; // "number" typeof 'hello'; // "string" typeof true; // "boolean" typeof undefined; // "undefined" typeof null; // "object" (历史bug) typeof Symbol(); // "symbol" typeof 1n; // "bigint" typeof {}; // "object" typeof []; // "object" typeof function(){}; // "function"

JavaScript是弱类型语言,支持隐式类型转换。常见转换场景:

三、运算符与流程控制

3.1 运算符分类

JavaScript提供丰富的运算符用于数据处理:

3.2 == 与 === 的区别

这是JavaScript最常被问及的面试题:

// == 宽松相等:先做类型转换再比较 1 == '1'; // true 0 == false; // true '' == false; // true null == undefined; // true // === 严格相等:不转换类型,类型不同直接返回false 1 === '1'; // false 0 === false; // false '' === false; // false null === undefined; // false

最佳实践:总是使用 === 和 !==,避免隐式类型转换带来的意外结果。

3.3 条件语句

// if/else if/else let score = 85; if (score >= 90) { console.log('优秀'); } else if (score >= 60) { console.log('及格'); } else { console.log('不及格'); } // switch let day = 3; switch (day) { case 1: console.log('周一'); break; case 2: console.log('周二'); break; default: console.log('其他'); } // 三元表达式 let status = age >= 18 ? '成年' : '未成年';

3.4 循环语句

// for循环 for (let i = 0; i < 5; i++) { console.log(i); // 0, 1, 2, 3, 4 } // while循环 let count = 0; while (count < 3) { console.log(count); count++; } // do-while(至少执行一次) let n = 0; do { console.log(n); n++; } while (n < 3); // for...of(遍历可迭代对象) for (const item of [1, 2, 3]) { console.log(item); } // for...in(遍历对象属性) for (const key in {a: 1, b: 2}) { console.log(key); }

break用于立即退出循环,continue用于跳过本次迭代进入下一次。

四、函数

4.1 函数声明与函数表达式

JavaScript中创建函数的两种主要方式:

// 函数声明(会被提升) function add(a, b) { return a + b; } // 函数表达式(不会被提升) const subtract = function(a, b) { return a - b; }; // 命名函数表达式 const multiply = function multiplyFn(a, b) { return a * b; };

函数声明会被提升到作用域顶部,因此在声明前就可以调用。函数表达式则不会提升,遵循变量作用域规则。

4.2 箭头函数

箭头函数是ES6引入的简洁语法,它不绑定自己的this,没有arguments对象,不能用作构造函数:

// 基础语法 const square = x => x * x; // 多参数需要括号 const sum = (a, b) => a + b; // 多语句需要花括号和return const getStats = (arr) => { const max = Math.max(...arr); const min = Math.min(...arr); return { max, min }; }; // this绑定差异(重要!) const obj = { name: 'Alice', greet1: function() { console.log(this.name); // 'Alice' — 指向obj }, greet2: () => { console.log(this.name); // undefined — 指向外层 } };

4.3 参数处理

// 默认参数 function greet(name = '访客') { console.log(`你好,${name}!`); } // 剩余参数(Rest Parameters) function sumAll(...numbers) { return numbers.reduce(function(total, n) { return total + n; }, 0); } console.log(sumAll(1, 2, 3, 4)); // 10 // arguments对象(仅普通函数) function showArgs() { console.log(arguments); // 类数组对象 }

4.4 高阶函数

高阶函数是接受函数作为参数或返回函数的函数。数组的map、filter、reduce是最常用的高阶函数:

const numbers = [1, 2, 3, 4, 5]; // map — 映射转换 const doubled = numbers.map(n => n * 2); // [2, 4, 6, 8, 10] // filter — 筛选过滤 const evens = numbers.filter(n => n % 2 === 0); // [2, 4] // reduce — 累积归约 const total = numbers.reduce(function(acc, cur) { return acc + cur; }, 0); // 15

4.5 闭包(Closure)

闭包是指内部函数可以访问外部函数作用域中变量的能力,即使外部函数已经执行完毕。闭包是JavaScript最强大的特性之一:

// 闭包示例:计数器 function createCounter() { let count = 0; return function() { count++; return count; }; } const counter = createCounter(); console.log(counter()); // 1 console.log(counter()); // 2 console.log(counter()); // 3 // 闭包应用:数据私有化 function createBankAccount(initialBalance) { let balance = initialBalance; return { deposit(amount) { balance += amount; return balance; }, withdraw(amount) { if (amount > balance) { return '余额不足'; } balance -= amount; return balance; }, getBalance() { return balance; } }; }

闭包的作用域链有三个层级:局部作用域(函数内部)→ 外部函数作用域 → 全局作用域。闭包的核心应用场景包括:数据私有化、函数柯里化、防抖与节流、模块模式。

4.6 IIFE(立即执行函数表达式)

// 立即执行函数 — 创建独立作用域 (function() { const privateVar = '外部无法访问'; console.log('立即执行'); })(); // 带参数的IIFE (function(name) { console.log(`你好,${name}`); })('JavaScript'); // 箭头函数IIFE (() => { console.log('箭头IIFE'); })();

IIFE主要用于创建独立的作用域,避免变量污染全局空间。在ES6模块普及之前,IIFE是JavaScript模块化的主要方式。

五、对象与数组

5.1 对象字面量与属性访问

// 创建对象 const person = { name: '张三', age: 28, 'full-name': '张三丰', greet() { console.log(`你好,我是${this.name}`); } }; // 属性访问 console.log(person.name); // 点号语法 console.log(person['name']); // 方括号语法 console.log(person['full-name']); // 含特殊字符必须用方括号 // 属性操作 person.email = 'zhangsan@example.com'; // 新增 person.age = 29; // 修改 delete person.email; // 删除 console.log('name' in person); // 检查 — true

5.2 数组方法详解

数组是JavaScript中最常用的数据结构,提供丰富的方法:

// 栈操作(后进先出) const stack = []; stack.push(1, 2, 3); // 尾部添加 → [1, 2, 3] stack.pop(); // 尾部移除 → 3, 数组变为[1, 2] // 队列操作(先进先出) const queue = []; queue.push(1, 2, 3); // 尾部添加 → [1, 2, 3] queue.shift(); // 头部移除 → 1, 数组变为[2, 3] queue.unshift(0); // 头部添加 → [0, 2, 3] // splice — 万能操作(插入/删除/替换) const arr = [1, 2, 3, 4, 5]; arr.splice(2, 1); // 从索引2删除1个元素 → [1, 2, 4, 5] arr.splice(1, 0, 'a', 'b'); // 在索引1插入a,b → [1, 'a', 'b', 2, 4, 5] arr.splice(2, 1, 'x'); // 替换索引2的1个元素为x // slice — 提取子数组(不修改原数组) const sub = arr.slice(1, 4); // 索引1到3(不含4) // concat / join const merged = [1, 2].concat([3, 4]); // [1, 2, 3, 4] const str = ['a', 'b', 'c'].join('-'); // 'a-b-c'

5.3 数组遍历与转换

const numbers = [1, 2, 3, 4, 5]; // forEach — 遍历(无返回值) numbers.forEach(n => console.log(n)); // map — 映射为新数组 const squares = numbers.map(n => n ** 2); // [1, 4, 9, 16, 25] // filter — 过滤元素 const bigNums = numbers.filter(n => n > 3); // [4, 5] // find — 查找第一个匹配元素 const found = numbers.find(n => n > 3); // 4 // some — 是否有任意元素满足条件 const hasEven = numbers.some(n => n % 2 === 0); // true // every — 是否所有元素满足条件 const allPositive = numbers.every(n => n > 0); // true

5.4 解构赋值

// 数组解构 const [a, b, ...rest] = [1, 2, 3, 4, 5]; console.log(a); // 1 console.log(b); // 2 console.log(rest); // [3, 4, 5] // 对象解构 const user = { name: 'Alice', age: 25, city: '北京' }; const { name, age, city: location } = user; console.log(name); // 'Alice' console.log(location); // '北京' // 嵌套解构 const data = { person: { name: 'Bob', scores: [90, 85] } }; const { person: { name: personName, scores: [math] } } = data; console.log(personName); // 'Bob' console.log(math); // 90

5.5 展开运算符

// 数组展开 const arr1 = [1, 2, 3]; const arr2 = [4, 5, 6]; const combined = [...arr1, ...arr2]; // [1, 2, 3, 4, 5, 6] // 对象展开 const defaults = { host: 'localhost', port: 3000 }; const config = { ...defaults, port: 8080, ssl: true }; // { host: 'localhost', port: 8080, ssl: true } // 函数调用中展开 const max = Math.max(...[3, 7, 1, 9, 4]); // 9

六、异步编程

6.1 异步编程概览

JavaScript是单线程语言,但通过事件循环机制实现了非阻塞的异步编程。异步编程的发展经历了三个阶段:回调函数 → Promise → async/await,每一次演进都让异步代码更加直观易读。

6.2 回调函数(Callback)

回调是最古老的异步模式,将函数作为参数传递给异步操作:

// 回调函数示例 function fetchData(callback) { setTimeout(() => { callback('数据加载完成'); }, 1000); } fetchData(function(result) { console.log(result); // 1秒后输出 }); // 回调地狱(Callback Hell) getUser(id, function(user) { getPosts(user.id, function(posts) { getComments(posts[0].id, function(comments) { console.log(comments); }); }); });

深层嵌套的回调会导致代码难以阅读和维护,即所谓的"回调地狱"(Callback Hell)。Promise正是为了解决这一问题而生。

6.3 Promise

Promise是ES6引入的标准异步解决方案,代表一个异步操作的最终完成或失败,及其结果值:

// 创建Promise const promise = new Promise((resolve, reject) => { setTimeout(() => { const success = true; if (success) { resolve('操作成功'); } else { reject('操作失败'); } }, 1000); }); // 使用Promise promise .then(result => console.log(result)) .catch(error => console.error(error)) .finally(() => console.log('完成')); // 链式调用 — 告别回调地狱 getUser(id) .then(user => getPosts(user.id)) .then(posts => getComments(posts[0].id)) .then(comments => console.log(comments)) .catch(err => console.error(err)); // Promise.all — 等待所有完成 const p1 = fetch('/api/users'); const p2 = fetch('/api/posts'); Promise.all([p1, p2]) .then(([users, posts]) => { console.log('全部加载完成'); }); // Promise.race — 取最先完成的 Promise.race([fetch('/api/data'), timeout(5000)]) .then(result => console.log(result)) .catch(err => console.log('请求超时'));

6.4 async/await

async/await是ES2017(ES8)引入的语法糖,让异步代码看起来像同步代码一样清晰:

// async函数 async function getUserData(userId) { try { const response = await fetch(`/api/users/${userId}`); const data = await response.json(); return data; } catch (error) { console.error('请求失败:', error); throw error; } } // 串行执行 async function loadSequential() { const user = await getUser(1); const posts = await getPosts(user.id); const comments = await getComments(posts[0].id); return comments; } // 并行执行(比串行更快) async function loadParallel() { const [users, posts] = await Promise.all([ fetch('/api/users'), fetch('/api/posts') ]); return { users: await users.json(), posts: await posts.json() }; } // 错误处理 async function safeLoad() { try { const data = await riskyOperation(); } catch (err) { console.error('操作失败,使用默认值'); return defaultValue; } }

6.5 事件循环(Event Loop)

事件循环是JavaScript异步编程的底层机制,理解它对于写出正确代码至关重要:

console.log('1'); // 同步任务 setTimeout(() => { console.log('2'); // 宏任务 }, 0); Promise.resolve().then(() => { console.log('3'); // 微任务 }); console.log('4'); // 同步任务 // 输出顺序:1 → 4 → 3 → 2

事件循环的执6行顺序:

  1. 同步代码(调用栈)优先执行
  2. 执行完所有同步代码后,检查微任务队列(Microtask Queue),依次执行所有微任务(Promise.then/catch/finally、queueMicrotask、MutationObserver)
  3. 微任务清空后,从宏任务队列(Macrotask Queue)取出一个任务执行(setTimeout、setInterval、I/O、UI渲染)
  4. 重复上述步骤

6.6 宏任务与微任务总结

任务类型 常见API 执行时机
同步任务 普通代码执行 立即执行
微任务 Promise.then/catch/finally、queueMicrotask、MutationObserver 当前宏任务结束后、下一个宏任务开始前
宏任务 setTimeout、setInterval、setImmediate、I/O、UI渲染 每次事件循环取一个执行

"理解事件循环和任务队列的优先级,是写出正确JavaScript异步代码的关键。记住:微任务优先于宏任务,Promise的回调属于微任务。"

总结

JavaScript核心语法是Web前端开发的基础。从变量声明到异步编程,每一步都至关重要。ES6+的引入使得JavaScript从一门简单的脚本语言进化为功能完备的现代编程语言,足以支撑从浏览器到服务端的全栈开发。

重点回顾:

1. 使用let/const替代var,遵循块级作用域规则

2. 始终使用 === 进行相等比较,避免隐式类型转换

3. 箭头函数简洁且不绑定this,适合回调场景

4. 闭包是JavaScript的核心特性,掌握它对理解作用域和模块化至关重要

5. 数组的map/filter/reduce是函数式编程的基石,优先于for循环

6. async/await是现代异步编程的标准写法,配合try/catch处理错误

7. 理解事件循环机制,区分宏任务与微任务的执行顺序