JavaScript 是 Web 的灵魂。从 ES6(ECMAScript 2015)开始,这门语言经历了巨大的进化。本文梳理 ES6 到现代 JavaScript 的核心知识点,适合有一两年经验的开发者系统复习。
一、变量声明:var / let / const
// ❌ 不要再使用 var
var x = 1; // 函数作用域,存在变量提升,可重复声明
// ✅ let:可变的块级作用域变量
let count = 0;
count = 1; // ✅ 可以重新赋值
// ✅ const:不可变的块级作用域常量
const API_URL = 'https://api.example.com';
// API_URL = 'other'; // ❌ TypeError: Assignment to constant variable
// 但 const 对象/数组的内部是可变的!
const user = { name: 'Alice' };
user.name = 'Bob'; // ✅ 修改属性是允许的
// user = {}; // ❌ 重新赋值不允许| 特性 | var |
let |
const |
|---|---|---|---|
| 作用域 | 函数 | 块 | 块 |
| 变量提升 | ✅(undefined) | ✅(TDZ) | ✅(TDZ) |
| 可重复声明 | ✅ | ❌ | ❌ |
| 可重新赋值 | ✅ | ✅ | ❌ |
| 必须初始化 | ❌ | ❌ | ✅ |
TDZ(Temporal Dead Zone):
let/const在声明之前不可访问,这个区域叫「暂时性死区」。
console.log(a); // undefined(var 提升)
var a = 1;
console.log(b); // ❌ ReferenceError(TDZ)
let b = 2;实用建议:默认使用
const,只有明确需要重新赋值时才用let。彻底告别var。
二、作用域与闭包
三种作用域
// 1. 全局作用域
const globalVar = '全局';
// 2. 函数作用域
function foo() {
const funcVar = '函数内';
console.log(globalVar); // ✅ 可以访问
}
// console.log(funcVar); // ❌ ReferenceError
// 3. 块级作用域(ES6 新增)
if (true) {
const blockVar = '块内';
let blockLet = '也是块内';
}
// console.log(blockVar); // ❌ ReferenceError闭包(Closure)
闭包 = 函数 + 其外部作用域的变量引用。
// 经典闭包:创建私有变量
function createCounter(initial = 0) {
let count = initial; // 私有变量
return {
increment() { return ++count; },
decrement() { return --count; },
get value() { return count; }
};
}
const counter = createCounter(10);
console.log(counter.increment()); // 11
console.log(counter.increment()); // 12
console.log(counter.value); // 12
// count 无法从外部直接访问!
// 闭包解决 var 循环问题
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// 输出:3, 3, 3(var 是函数作用域,共享同一个 i)
// 解决方案 1:let(块级作用域)
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// 输出:0, 1, 2
// 解决方案 2:闭包
for (var i = 0; i < 3; i++) {
((j) => setTimeout(() => console.log(j), 100))(i);
}
// 输出:0, 1, 2三、箭头函数、模板字符串、解构、展开运算符
箭头函数
// 传统函数
function add(a, b) { return a + b; }
// 箭头函数
const add = (a, b) => a + b; // 单行隐式返回
const greet = name => `Hello, ${name}`; // 单参数省略括号
const noArgs = () => console.log('Hi'); // 无参数需要空括号
// 多行需要 {} 和显式 return
const complex = (a, b) => {
const result = a * b;
return result > 100 ? result : 0;
};
// ⚠️ 箭头函数没有自己的 this
const obj = {
name: 'Alice',
greet: function() {
console.log(this.name); // 'Alice'
},
greetArrow: () => {
console.log(this.name); // undefined(this 指向外层)
}
};| 特性 | 普通函数 | 箭头函数 |
|---|---|---|
this |
动态绑定(调用者) | 词法绑定(定义时) |
arguments |
✅ | ❌ |
new 调用 |
✅ | ❌ |
prototype |
✅ | ❌ |
| 适用场景 | 方法、构造函数、事件处理器 | 回调、map/filter、简短函数 |
模板字符串
const name = 'Alice';
const age = 25;
// 旧方式
const old = 'My name is ' + name + ', I\'m ' + age + ' years old.';
// 模板字符串
const modern = `My name is ${name}, I'm ${age} years old.`;
// 多行字符串
const html = `
<div class="card">
<h2>${name}</h2>
<p>Age: ${age}</p>
</div>
`;
// 标签模板
function highlight(strings, ...values) {
return strings.reduce((result, str, i) =>
result + str + (values[i] ? `<mark>${values[i]}</mark>` : ''), '');
}
highlight`Hello, ${name}! You are ${age}.`;
// "Hello, <mark>Alice</mark>! You are <mark>25</mark>."解构赋值
// 对象解构
const user = { name: 'Alice', age: 25, city: 'Beijing' };
const { name, age, city: location = 'Unknown' } = user;
// name='Alice', age=25, location='Beijing'(重命名+默认值)
// 嵌套解构
const response = {
data: { user: { profile: { avatar: 'url', bio: 'hello' } } }
};
const { data: { user: { profile: { avatar, bio } } } } = response;
// 数组解构
const colors = ['red', 'green', 'blue'];
const [first, , third] = colors; // first='red', third='blue'
const [head, ...rest] = colors; // head='red', rest=['green','blue']
// 函数参数直接解构
function display({ name, age, city = 'Unknown' }) {
console.log(`${name}, ${age}, ${city}`);
}
display(user); // "Alice, 25, Beijing"
// 交换变量(无需临时变量)
let a = 1, b = 2;
[a, b] = [b, a]; // a=2, b=1展开运算符
// 数组展开
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const merged = [...arr1, ...arr2]; // [1,2,3,4,5,6]
const copy = [...arr1]; // 浅拷贝
// 对象展开
const defaults = { theme: 'light', lang: 'zh' };
const config = { ...defaults, lang: 'en' }; // lang 被覆盖
// { theme: 'light', lang: 'en' }
// 函数参数展开
const numbers = [5, 12, 8, 130, 44];
Math.max(...numbers); // 130(替代 apply)四、Promise 与 async/await
Promise 基础
// 创建 Promise
const fetchUser = (id) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (id > 0) {
resolve({ id, name: 'Alice' });
} else {
reject(new Error('Invalid ID'));
}
}, 1000);
});
};
// 链式调用
fetchUser(1)
.then(user => {
console.log(user.name);
return fetchUser(2); // 返回新 Promise
})
.then(user => console.log(user.name))
.catch(err => console.error('Error:', err.message))
.finally(() => console.log('Done'));
// Promise 静态方法
Promise.all([fetchUser(1), fetchUser(2)])
.then(([u1, u2]) => console.log(u1, u2));
// 全部成功才成功
Promise.allSettled([fetchUser(1), fetchUser(-1)])
.then(results => results.forEach(r =>
console.log(r.status, r.value || r.reason)));
// 等待全部完成,不管成败
Promise.race([fetchUser(1), timeout(5000)])
.then(user => console.log(user));
// 第一个完成的决定结果
Promise.any([fetchUser(-1), fetchUser(1)])
.then(user => console.log(user));
// 只要有一个成功就成功| 方法 | 行为 | 使用场景 |
|---|---|---|
Promise.all |
全部成功 → resolve;任一失败 → reject | 并行请求,全部需要 |
Promise.allSettled |
全部完成(不管成败) | 批量操作,需要每个结果 |
Promise.race |
第一个完成决定结果 | 超时控制 |
Promise.any |
任一成功 → resolve;全失败 → reject | 多源请求,取最快成功 |
async/await
// 异步函数:返回 Promise
async function loadUserData(id) {
try {
const user = await fetchUser(id);
const posts = await fetchPosts(user.id); // 串行
return { user, posts };
} catch (error) {
console.error('Load failed:', error.message);
throw error; // 重新抛出
}
}
// 并行请求
async function loadDashboard() {
const [user, stats, notifications] = await Promise.all([
fetchUser(1),
fetchStats(),
fetchNotifications()
]);
return { user, stats, notifications };
}
// 顶层 await(ES2022,ESM 模块中)
// const config = await fetch('/api/config').then(r => r.json());
// 错误处理模式
const [error, data] = await fetchUser(1)
.then(data => [null, data])
.catch(err => [err, null]);
if (error) { /* 处理错误 */ }实用建议:
- 永远不要忘记
try/catch或.catch()。- 没有依赖关系的异步操作用
Promise.all并行。- 在
forEach/map中使用async时要小心——它们不会等待。
五、模块化(ESM)
// ===== math.js =====
export const PI = 3.14159;
export function add(a, b) {
return a + b;
}
export function multiply(a, b) {
return a * b;
}
// 默认导出(每个模块只能有一个)
export default function greet(name) {
return `Hello, ${name}!`;
}
// ===== app.js =====
// 命名导入
import { add, multiply, PI } from './math.js';
// 重命名
import { add as sum } from './math.js';
// 全部导入
import * as MathUtils from './math.js';
// 默认导入
import greet from './math.js';
// 混合导入
import greet, { add, PI } from './math.js';
// 动态导入(返回 Promise)
const module = await import('./math.js');| 特性 | CommonJS (CJS) | ES Modules (ESM) |
|---|---|---|
| 语法 | require / module.exports |
import / export |
| 加载时机 | 运行时同步 | 编译时静态分析 |
| Tree Shaking | ❌ | ✅ |
| 顶层 await | ❌ | ✅ |
| Node.js | 默认 | .mjs 或 "type":"module" |
| 浏览器 | ❌(需打包) | ✅ 原生支持 |
六、数组方法(map / filter / reduce)
const users = [
{ id: 1, name: 'Alice', age: 25, active: true },
{ id: 2, name: 'Bob', age: 30, active: false },
{ id: 3, name: 'Charlie', age: 35, active: true },
{ id: 4, name: 'Diana', age: 28, active: true },
];
// map:转换每个元素
const names = users.map(u => u.name);
// ['Alice', 'Bob', 'Charlie', 'Diana']
// filter:筛选
const activeUsers = users.filter(u => u.active);
// [{id:1,...}, {id:3,...}, {id:4,...}]
// find:查找第一个匹配
const bob = users.find(u => u.name === 'Bob');
// {id:2,...}
// reduce:归约
const totalAge = users.reduce((sum, u) => sum + u.age, 0); // 118
// 链式组合
const activeNames = users
.filter(u => u.active)
.map(u => u.name);
// ['Alice', 'Charlie', 'Diana']
// some / every:断言
users.some(u => u.age > 40); // false(存在一个?)
users.every(u => u.age > 20); // true(全部满足?)
// flat / flatMap
const nested = [[1, 2], [3, 4]];
nested.flat(); // [1,2,3,4]
const words = ['hello world', 'foo bar'];
words.flatMap(s => s.split(' ')); // ['hello','world','foo','bar']
// sort:排序(注意:默认按字符串排序!)
users.sort((a, b) => a.age - b.age); // 按年龄升序
// 实用技巧:数组去重
const unique = [...new Set([1, 2, 2, 3, 3, 3])]; // [1,2,3]七、事件循环(Event Loop)
console.log('1');
setTimeout(() => console.log('2'), 0);
Promise.resolve().then(() => console.log('3'));
console.log('4');
// 输出顺序:1 → 4 → 3 → 2执行顺序:
- 同步代码(调用栈)→ 先执行完
- 微任务(Microtasks):
Promise.then/catch/finally、MutationObserver、queueMicrotask - 宏任务(Macrotasks):
setTimeout、setInterval、I/O、UI 渲染
// 更复杂的例子
console.log('start');
setTimeout(() => {
console.log('timeout1');
Promise.resolve().then(() => console.log('promise in timeout'));
}, 0);
Promise.resolve()
.then(() => console.log('promise1'))
.then(() => console.log('promise2'));
console.log('end');
// 输出:
// start → end → promise1 → promise2 → timeout1 → promise in timeout| 类型 | 示例 | 优先级 |
|---|---|---|
| 同步代码 | 普通函数调用 | 最先 |
| 微任务 | Promise.then, queueMicrotask |
每个宏任务后,下一个宏任务前 |
| 宏任务 | setTimeout, setInterval, I/O |
最后 |
| 渲染 | requestAnimationFrame |
宏任务之间(浏览器环境) |
实用建议:理解 Event Loop 是处理异步 bug 的关键。当遇到「先执行了什么后执行了什么」的问题时,画出调用栈、微任务队列和宏任务队列。
八、原型链与 class
原型链
// JavaScript 的继承基于原型链
const arr = [1, 2, 3];
// arr → Array.prototype → Object.prototype → null
arr.push(4); // 继承自 Array.prototype
arr.toString(); // 继承自 Object.prototype
// 原型链查找
const obj = { a: 1 };
console.log(obj.hasOwnProperty('a')); // true(自己的属性)
console.log(obj.hasOwnProperty('toString')); // false(继承的)
console.log('toString' in obj); // trueclass(语法糖)
class Animal {
// 私有字段(ES2022)
#id;
constructor(name) {
this.name = name;
this.#id = Symbol(name);
}
// 实例方法
speak() {
console.log(`${this.name} makes a sound.`);
}
// Getter
get id() {
return this.#id;
}
// 静态方法
static create(name) {
return new Animal(name);
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name); // 必须先调用 super
this.breed = breed;
}
// 方法重写
speak() {
super.speak(); // 调用父类方法
console.log(`${this.name} barks.`);
}
// 静态方法也可以继承
}
const dog = new Dog('Max', 'Golden Retriever');
dog.speak();
// Max makes a sound.
// Max barks.
console.log(dog instanceof Dog); // true
console.log(dog instanceof Animal); // true九、Map / Set / WeakMap
// ===== Set:唯一值集合 =====
const set = new Set([1, 2, 2, 3]);
console.log(set); // Set { 1, 2, 3 }
set.add(4);
set.has(2); // true
set.delete(3);
set.size; // 3
// 数组去重
const unique = [...new Set([1,2,2,3,3,3])]; // [1,2,3]
// ===== Map:键值对(键可以是任意类型) =====
const map = new Map();
map.set('name', 'Alice');
map.set(42, 'answer');
map.set({ id: 1 }, 'object key');
map.get('name'); // 'Alice'
map.has(42); // true
map.delete(42);
map.size; // 2
// 遍历
for (const [key, value] of map) {
console.log(key, value);
}
// ===== WeakMap:键必须是对象,弱引用 =====
const weakMap = new WeakMap();
let obj = { id: 1 };
weakMap.set(obj, 'metadata');
obj = null; // 垃圾回收时,weakMap 中的条目也会被回收
// WeakMap 不可遍历,没有 size
// 使用场景:存储 DOM 元素关联数据
const elementData = new WeakMap();
elementData.set(document.querySelector('#app'), {
clickCount: 0,
lastClick: null
});| 数据结构 | 键类型 | 可遍历 | 垃圾回收 | 典型场景 |
|---|---|---|---|---|
Object |
String/Symbol | ✅ | ❌ | 通用键值 |
Map |
任意类型 | ✅ | ❌ | 需要非字符串键 |
WeakMap |
仅 Object | ❌ | ✅ | DOM 元数据、私有数据 |
Set |
任意类型 | ✅ | ❌ | 去重、集合运算 |
十、最佳实践速查
| 实践 | 说明 |
|---|---|
✅ const 优先,let 其次 |
明确不可变性意图 |
✅ 使用 === 而非 == |
避免隐式类型转换 |
✅ async/await 替代回调地狱 |
可读性大幅提升 |
✅ for...of + Object.entries 遍历 |
替代传统 for 循环 |
✅ 可选链 ?. 和空值合并 ?? |
安全访问深层属性 |
| ✅ 使用 ESM 模块化 | Tree Shaking 友好 |
❌ 避免 eval() |
安全风险和性能问题 |
| ❌ 不要修改内置原型 | 破坏他人代码 |
❌ 避免 with 语句 |
已弃用,作用域混乱 |
结语
JavaScript 从「10 天设计出来的语言」到如今最活跃的生态系统,其核心概念始终没变:原型链、闭包、事件循环、异步编程。理解这些,你就掌握了 JavaScript 的灵魂。建议常去 MDN 查阅文档,那是前端开发者最好的朋友。
下一篇我们将进入 Vue 系列,探索组件化开发的魅力!