JavaScript 自 1995 年诞生以来,经历了漫长的演化。2015 年 ECMAScript 6(又称 ES2015)的发布,标志着 JavaScript 语言进入现代阶段。此后 TC39 委员会采用年度发布节奏,每年为语言带来渐进式增强。本文将系统梳理从 ES2015 到 ES2024 的所有重要特性,每个知识点都配有完整可运行的代码示例。
一、ES2015(ES6)核心特性
ES2015 是 JavaScript 历史上规模最大的一次更新,引入了大量新语法和 API,从根本上改变了 JavaScript 的编程范式。
1.1 let / const —— 块级作用域与暂时性死区
在 ES6 之前,JavaScript 只有函数作用域和全局作用域,var 声明的变量存在变量提升(hoisting)和函数级作用域问题。let 和 const 引入了块级作用域。
// 块级作用域示例
{
var a = 1; // 函数/全局作用域
let b = 2; // 块级作用域
const c = 3; // 块级作用域,不可重新赋值
}
console.log(a); // 1
// console.log(b); // ReferenceError: b is not defined
// console.log(c); // ReferenceError: c is not defined
// 暂时性死区(Temporal Dead Zone, TDZ)
console.log(typeof x); // undefined(var 提升)
// console.log(typeof y); // ReferenceError(TDZ)
let y = 10;
// const 必须初始化,且不能重新赋值
const PI = 3.14159;
// PI = 3; // TypeError: Assignment to constant variable
// 但 const 对象属性可变
const obj = { name: 'Alice' };
obj.name = 'Bob'; // 允许
// obj = {}; // 错误典型应用场景:let 用于循环计数器、临时变量;const 用于常量、函数表达式、模块导入、原则上所有不应重新赋值的变量。
// let 在循环中产生正确闭包
for (let i = 0; i < 5; i++) {
setTimeout(() => console.log(i), 0); // 0, 1, 2, 3, 4
}
// var 在循环中产生错误闭包
for (var j = 0; j < 5; j++) {
setTimeout(() => console.log(j), 0); // 5, 5, 5, 5, 5
}1.2 箭头函数
箭头函数提供了更简洁的函数语法,并解决了传统函数中 this 绑定的痛点。
语法形式:
// 无参数
const f1 = () => 'hello';
// 单个参数(可省略括号)
const f2 = x => x * 2;
// 多个参数
const f3 = (a, b) => a + b;
// 多行函数体需用花括号 + return
const f4 = (a, b) => {
const sum = a + b;
return sum * 2;
};
// 返回对象字面量需加括号
const f5 = () => ({ id: 1, name: 'test' });this 绑定机制:箭头函数没有自己的 this,它会捕获定义时所在上下文的 this 值,且一旦绑定不可改变(call、apply、bind 无效)。
function Timer() {
this.seconds = 0;
// 传统函数:this 指向调用者(setTimeout 的全局对象)
setInterval(function() {
this.seconds++; // 错误:this 指向 window/global
}, 1000);
// 箭头函数:this 继承自 Timer 实例
setInterval(() => {
this.seconds++; // 正确
}, 1000);
}适用场景:
- 回调函数(setTimeout、setInterval、事件监听)
- 数组方法(map、filter、reduce)
- React 类组件的自定义方法
不适用场景:
- 作为对象方法(
this不会指向对象) - 作为构造函数(不能使用
new) - 需要动态
this的场景(如事件处理中需要event.currentTarget) - 需要使用
arguments对象时(箭头函数无arguments)
// 不适用于对象方法
const obj = {
name: 'Alice',
greet: () => `Hello, ${this.name}`, // this 指向外部,不是 obj
greetOk() {
return `Hello, ${this.name}`; // 正确
}
};
// 没有 arguments
const sum = () => {
// console.log(arguments); // ReferenceError
};
// 不能作为构造函数
// const p = new (() => {}); // TypeError1.3 模板字符串
模板字符串使用反引号 ` 包裹,支持插值表达式、多行字符串和标签模板。
const name = 'World';
const greeting = `Hello, ${name}!`; // Hello, World!
// 多行字符串
const multiLine = `
<div>
<p>Hello</p>
</div>
`;
// 表达式插值
const a = 5, b = 10;
console.log(`${a} + ${b} = ${a + b}`); // 5 + 10 = 15
// 三元表达式
const score = 85;
const grade = `成绩: ${score >= 60 ? '及格' : '不及格'}`;
// 函数调用
const formatPrice = p => `¥${p.toFixed(2)}`;
console.log(formatPrice(19.9)); // ¥19.90标签模板:允许用函数处理模板字符串,在 DSL、国际化、安全过滤等场景非常强大。
function highlight(strings, ...values) {
return strings.reduce((result, str, i) => {
const value = values[i] ? `<strong>${values[i]}</strong>` : '';
return result + str + value;
}, '');
}
const user = 'Alice';
const action = 'login';
const result = highlight`用户 ${user} 执行了 ${action} 操作`;
console.log(result); // 用户 <strong>Alice</strong> 执行了 <strong>login</strong> 操作1.4 解构赋值
解构赋值可以从数组或对象中提取值并赋给变量,极大简化数据访问代码。
数组解构:
const arr = [1, 2, 3, 4, 5];
// 基本解构
const [first, second] = arr;
console.log(first, second); // 1 2
// 跳过元素
const [a, , c] = arr;
console.log(a, c); // 1 3
// 剩余模式
const [head, ...tail] = arr;
console.log(head, tail); // 1 [2, 3, 4, 5]
// 默认值
const [x = 0, y = 0, z = 0] = [1, 2];
console.log(x, y, z); // 1 2 0
// 交换变量
let p = 1, q = 2;
[p, q] = [q, p];
console.log(p, q); // 2 1对象解构:
const person = { name: 'Alice', age: 30, city: 'Beijing' };
// 基本解构
const { name, age } = person;
console.log(name, age); // Alice 30
// 重命名
const { name: userName, age: userAge } = person;
console.log(userName, userAge); // Alice 30
// 默认值
const { country = 'China' } = person;
console.log(country); // China
// 嵌套解构
const data = {
id: 1,
meta: { title: 'ES6 Guide', views: 1000 }
};
const { meta: { title, views } } = data;
console.log(title, views); // ES6 Guide 1000
// 剩余模式
const { name: n, ...rest } = person;
console.log(n, rest); // Alice { age: 30, city: 'Beijing' }
// 函数参数解构
function printUser({ name, age, city = 'Unknown' }) {
console.log(`${name} (${age}) - ${city}`);
}
printUser(person); // Alice (30) - Beijing1.5 展开运算符与剩余参数
展开运算符(spread operator)和剩余参数(rest parameters)都使用 ... 语法,但用途不同。
展开运算符:将可迭代对象展开为多个元素。
// 数组展开
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const merged = [...arr1, ...arr2]; // [1, 2, 3, 4, 5, 6]
// 复制数组(浅拷贝)
const copy = [...arr1];
// 字符串展开
const chars = [...'hello']; // ['h', 'e', 'l', 'l', 'o']
// 对象展开(ES2018)
const obj1 = { a: 1, b: 2 };
const obj2 = { c: 3, ...obj1 }; // { c: 3, a: 1, b: 2 }
// 合并对象(后面的覆盖前面的)
const mergedObj = { ...obj1, ...obj2 };
// 函数调用展开
const nums = [1, 2, 3, 4, 5];
console.log(Math.max(...nums)); // 5剩余参数:将多个参数收集为一个数组。
function sum(...numbers) {
return numbers.reduce((total, n) => total + n, 0);
}
console.log(sum(1, 2, 3, 4)); // 10
// 剩余参数必须放在最后
function format(template, ...args) {
return template.replace(/%s/g, () => args.shift());
}
console.log(format('Hello %s, you are %s', 'Alice', 30));
// Hello Alice, you are 30
// 与解构结合
function processConfig({ debug, ...options }) {
console.log(debug, options);
}
processConfig({ debug: true, port: 3000, host: 'localhost' });
// true { port: 3000, host: 'localhost' }1.6 默认参数
ES6 允许在函数参数中直接指定默认值,且默认值可以是表达式,也可以引用前面的参数。
function greet(name = 'Guest', greeting = 'Hello') {
return `${greeting}, ${name}!`;
}
console.log(greet()); // Hello, Guest!
console.log(greet('Alice')); // Hello, Alice!
console.log(greet('Bob', 'Hi')); // Hi, Bob!
// 默认值可以是表达式
function createId(prefix = 'usr', timestamp = Date.now()) {
return `${prefix}_${timestamp}`;
}
// 默认值引用前面的参数
function multiply(a, b = a * 2) {
return a * b;
}
console.log(multiply(3)); // 3 * 6 = 18
// 与解构结合
function fetchData(url, { method = 'GET', timeout = 3000 } = {}) {
console.log(`${method} ${url} (timeout: ${timeout}ms)`);
}
fetchData('/api/users'); // GET /api/users (timeout: 3000ms)
fetchData('/api/users', { method: 'POST' }); // POST /api/users (timeout: 3000ms)
// 注意:null 不会触发默认值,但 undefined 会
function test(x = 10) {
return x;
}
console.log(test(undefined)); // 10
console.log(test(null)); // null1.7 Object.assign 与对象字面量增强
Object.assign:用于对象的合并(浅拷贝)。
const target = { a: 1 };
const source1 = { b: 2 };
const source2 = { c: 3, a: 4 };
Object.assign(target, source1, source2);
console.log(target); // { a: 4, b: 2, c: 3 }
// 常见用途:克隆对象(浅拷贝)
const clone = Object.assign({}, target);
// 合并默认配置
const DEFAULTS = { method: 'GET', timeout: 3000 };
const config = Object.assign({}, DEFAULTS, { method: 'POST' });对象字面量增强:
const name = 'Alice';
const age = 30;
// 属性简写
const person = { name, age }; // 等同于 { name: name, age: age }
// 方法简写
const calculator = {
add(a, b) { return a + b; }, // 等同于 add: function(a, b) { ... }
subtract(a, b) { return a - b; }
};
// 计算属性名
const key = 'dynamicKey';
const obj = {
[key]: 'value',
[`${key}2`]: 'value2'
};
console.log(obj); // { dynamicKey: 'value', dynamicKey2: 'value2' }二、Symbol 类型
Symbol 是 ES6 新增的第七种原始数据类型,表示唯一且不可变的值,常用作对象属性的唯一键。
2.1 基本用法
const s1 = Symbol();
const s2 = Symbol();
console.log(s1 === s2); // false
const s3 = Symbol('description');
const s4 = Symbol('description');
console.log(s3 === s4); // false(描述相同,值不同)
console.log(s3.toString()); // Symbol(description)
console.log(s3.description); // 'description'(ES2019)
// 作为对象属性键
const secret = Symbol('secret');
const user = {
name: 'Alice',
[secret]: 'hidden_value'
};
console.log(user[secret]); // hidden_value
console.log(Object.keys(user)); // ['name'] — Symbol 键不会被枚举
console.log(JSON.stringify(user)); // {"name":"Alice"}
console.log(Object.getOwnPropertySymbols(user)); // [Symbol(secret)]2.2 Symbol.for() 与 Symbol.keyFor()
Symbol.for() 会在全局 Symbol 注册表中查找/创建 Symbol,相同键名返回同一个 Symbol。
const s1 = Symbol.for('app.key');
const s2 = Symbol.for('app.key');
console.log(s1 === s2); // true
const s3 = Symbol('app.key');
console.log(s1 === s3); // false
// Symbol.keyFor() 返回全局注册表中的键名
console.log(Symbol.keyFor(s1)); // 'app.key'
console.log(Symbol.keyFor(s3)); // undefined(非全局注册表)2.3 内置 Symbol
JavaScript 内置了一系列 well-known Symbol,用于自定义语言内部行为。
// Symbol.iterator — 使对象可迭代
const customIterable = {
*[Symbol.iterator]() {
yield 1;
yield 2;
yield 3;
}
};
console.log([...customIterable]); // [1, 2, 3]
// Symbol.toStringTag — 自定义 toString 标签
class MyArray {
get [Symbol.toStringTag]() {
return 'MyArray';
}
}
const ma = new MyArray();
console.log(Object.prototype.toString.call(ma)); // [object MyArray]
// Symbol.hasInstance — 自定义 instanceof 行为
class MyNumber {
static [Symbol.hasInstance](instance) {
return typeof instance === 'number';
}
}
console.log(123 instanceof MyNumber); // true
console.log('abc' instanceof MyNumber); // false
// Symbol.toPrimitive — 自定义类型转换
const obj = {
[Symbol.toPrimitive](hint) {
if (hint === 'number') return 42;
if (hint === 'string') return 'forty-two';
return 'default';
}
};
console.log(+obj); // 42
console.log(`${obj}`); // forty-two
console.log(obj + ''); // 'default'
// Symbol.species — 控制派生对象的构造函数
class MyArray2 extends Array {
static get [Symbol.species]() { return Array; }
}
const ma2 = new MyArray2(1, 2, 3);
const mapped = ma2.map(x => x * 2);
console.log(mapped instanceof MyArray2); // false
console.log(mapped instanceof Array); // true三、Set、Map、WeakSet、WeakMap
ES6 引入了四种新的集合类型,弥补了传统 Object 和 Array 在数据管理上的不足。
3.1 Set
Set 是值不能重复的有序列表。
const set = new Set();
// 添加
set.add(1);
set.add(2);
set.add(2); // 被忽略
set.add('2'); // 类型不同,可以添加
console.log(set.size); // 3
// 判断是否存在
console.log(set.has(1)); // true
console.log(set.has(3)); // false
// 删除
set.delete(2);
console.log(set.has(2)); // false
// 遍历
set.add(3).add(4);
set.forEach(value => console.log(value)); // 1, '2', 3, 4
for (const item of set) {
console.log(item);
}
// 转为数组
const arr = [...set];
const arr2 = Array.from(set);
// 清空
// set.clear();
// 去重
const unique = [...new Set([1, 2, 2, 3, 1, 4])];
console.log(unique); // [1, 2, 3, 4]
// 并集、交集、差集
const a = new Set([1, 2, 3]);
const b = new Set([3, 4, 5]);
const union = new Set([...a, ...b]); // {1, 2, 3, 4, 5}
const intersect = new Set([...a].filter(x => b.has(x))); // {3}
const difference = new Set([...a].filter(x => !b.has(x))); // {1, 2}3.2 Map
Map 是键值对集合,键可以是任意类型(包括对象、函数、NaN),而 Object 的键只能是字符串或 Symbol。
const map = new Map();
// 添加/设置
map.set('name', 'Alice');
map.set(123, 'number key');
map.set({ id: 1 }, 'object key');
map.set(undefined, 'undefined key');
map.set(NaN, 'NaN key');
// 获取
console.log(map.get('name')); // Alice
console.log(map.get(123)); // 'number key'
console.log(map.get(NaN)); // 'NaN key'(NaN === NaN 在 Map 中为 true)
// 判断
console.log(map.has('name')); // true
// 大小
console.log(map.size); // 5
// 删除
map.delete(123);
// 遍历
const m = new Map([['a', 1], ['b', 2], ['c', 3]]);
m.forEach((value, key) => console.log(`${key}: ${value}`));
for (const [key, value] of m) {
console.log(key, value);
}
for (const key of m.keys()) { }
for (const value of m.values()) { }
for (const [k, v] of m.entries()) { }
// 与 Object 转换
const obj = Object.fromEntries(m); // { a: 1, b: 2, c: 3 }
const map2 = new Map(Object.entries(obj));
// Map 比 Object 更适合频繁增删键值对的场景3.3 WeakSet 与 WeakMap
WeakSet 和 WeakMap 的键是弱引用,不会阻止垃圾回收,适用于需要关联额外数据但不影响对象生命周期的场景。
WeakMap:
// WeakMap 的键必须是对象
const wm = new WeakMap();
let obj = { id: 1 };
wm.set(obj, 'some metadata');
console.log(wm.get(obj)); // 'some metadata'
obj = null; // 对象被回收,WeakMap 自动移除该条目
// 没有 size 属性,没有 forEach,不可遍历
// console.log(wm.size); // undefined实际应用:存储私有属性:
const privateData = new WeakMap();
class User {
constructor(name, password) {
privateData.set(this, { password });
this.name = name;
}
checkPassword(pwd) {
return privateData.get(this).password === pwd;
}
}
const user = new User('Alice', 'secret123');
console.log(user.checkPassword('secret123')); // true
console.log(user.password); // undefined(私密)实际应用:DOM 关联数据:
// 假设在浏览器环境中
const elementData = new WeakMap();
function attachData(element, data) {
elementData.set(element, data);
}
function getData(element) {
return elementData.get(element);
}
// 当 DOM 元素被移除时,关联数据自动被垃圾回收WeakSet:类似 WeakMap,但只存储对象值,常用于标记对象。
const visited = new WeakSet();
class Tracker {
track(obj) {
visited.add(obj);
}
hasVisited(obj) {
return visited.has(obj);
}
}3.4 性能对比
| 特性 | Object | Map | Array | Set |
|---|---|---|---|---|
| 键类型 | String/Symbol | 任意 | 数字索引 | 任意值 |
| 插入顺序 | 普通属性有序,整数属性特殊 | 有序 | 有序 | 有序 |
| 大小获取 | 手动计算 | .size |
.length |
.size |
| 查找性能 | O(1) | O(1) | O(n) | O(1) |
| 频繁增删 | 较差 | 优化 | 一般 | 优化 |
| 迭代 | 需要 Object.keys() |
直接可迭代 | 直接可迭代 | 直接可迭代 |
四、迭代器与生成器
迭代器(Iterator)和生成器(Generator)为 JavaScript 提供了统一的迭代协议和惰性求值能力。
4.1 Iterator 协议
一个对象要实现 Iterator 协议,必须有一个 next() 方法,返回 { value: any, done: boolean } 格式的对象。
// 手动实现一个迭代器
function createIterator(array) {
let index = 0;
return {
next() {
if (index < array.length) {
return { value: array[index++], done: false };
} else {
return { value: undefined, done: true };
}
}
};
}
const iterator = createIterator(['a', 'b', 'c']);
console.log(iterator.next()); // { value: 'a', done: false }
console.log(iterator.next()); // { value: 'b', done: false }
console.log(iterator.next()); // { value: 'c', done: false }
console.log(iterator.next()); // { value: undefined, done: true }4.2 可迭代协议(Iterable Protocol)
对象实现了 [Symbol.iterator] 方法且返回一个迭代器,就是可迭代对象。可以用 for...of、展开运算符等消费。
// 原生可迭代对象
console.log(typeof [][Symbol.iterator]); // 'function'
console.log(typeof ''[Symbol.iterator]); // 'function'
console.log(typeof new Set()[Symbol.iterator]); // 'function'
console.log(typeof new Map()[Symbol.iterator]); // 'function'
// 自定义可迭代对象
class Range {
constructor(start, end) {
this.start = start;
this.end = end;
}
[Symbol.iterator]() {
let current = this.start;
const end = this.end;
return {
next() {
if (current <= end) {
return { value: current++, done: false };
} else {
return { value: undefined, done: true };
}
}
};
}
}
const range = new Range(1, 5);
console.log([...range]); // [1, 2, 3, 4, 5]
for (const n of range) {
console.log(n); // 1, 2, 3, 4, 5
}4.3 Generator 函数
Generator 函数使用 function* 定义,返回一个 Generator 对象,既是迭代器也是可迭代对象。通过 yield 暂停函数执行,通过 next() 恢复执行。
// 基本用法
function* numberGenerator() {
yield 1;
yield 2;
yield 3;
}
const gen = numberGenerator();
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: false }
console.log(gen.next()); // { value: undefined, done: true }
// 可用 for...of 消费
for (const n of numberGenerator()) {
console.log(n); // 1, 2, 3
}
// yield 表达式可以接收 next() 传入的值
function* interactiveGen() {
const a = yield '请输入第一个数';
const b = yield '请输入第二个数';
yield `结果是: ${a + b}`;
}
const gi = interactiveGen();
console.log(gi.next()); // { value: '请输入第一个数', done: false }
console.log(gi.next(10)); // { value: '请输入第二个数', done: false }
console.log(gi.next(20)); // { value: '结果是: 30', done: false }
console.log(gi.next()); // { value: undefined, done: true }4.4 生成器实现无限序列
生成器的惰性求值特性使其非常适合表示无限序列。
// 无限斐波那契数列
function* fibonacci() {
let a = 0, b = 1;
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
const fib = fibonacci();
console.log(fib.next().value); // 0
console.log(fib.next().value); // 1
console.log(fib.next().value); // 1
console.log(fib.next().value); // 2
console.log(fib.next().value); // 3
console.log(fib.next().value); // 5
// 取前10个
const first10 = [...(function*() {
for (let i = 0; i < 10; i++) yield fib.next().value;
})()];
// 无限自然数序列
function* naturalNumbers(start = 0) {
while (true) yield start++;
}
// 取前 N 个
function take(n, iterable) {
const iterator = iterable[Symbol.iterator]();
const result = [];
for (let i = 0; i < n; i++) {
const { value, done } = iterator.next();
if (done) break;
result.push(value);
}
return result;
}
console.log(take(5, naturalNumbers())); // [0, 1, 2, 3, 4]
// 无限随机数
function* randomStream(seed = Date.now()) {
let s = seed;
while (true) {
s = (s * 16807) % 2147483647;
yield s / 2147483647;
}
}4.5 yield* 委托
yield* 可以将迭代控制委托给另一个可迭代对象或生成器。
function* gen1() {
yield 1;
yield 2;
}
function* gen2() {
yield 0;
yield* gen1(); // 委托给 gen1
yield* [3, 4]; // 委托给数组
yield 5;
}
console.log([...gen2()]); // [0, 1, 2, 3, 4, 5]
// 递归生成器
function* flatten(arr) {
for (const item of arr) {
if (Array.isArray(item)) {
yield* flatten(item); // 递归
} else {
yield item;
}
}
}
const nested = [1, [2, [3, 4], 5], 6];
console.log([...flatten(nested)]); // [1, 2, 3, 4, 5, 6]五、Proxy 与 Reflect
Proxy 和 Reflect 是 ES6 引入的元编程(metaprogramming)能力,允许拦截和自定义对象的基本操作。
5.1 Proxy 基础
Proxy 用于创建一个对象的代理,拦截对该对象的各种操作。
const target = { name: 'Alice', age: 30 };
const handler = {
// 拦截属性读取
get(target, prop, receiver) {
console.log(`GET ${String(prop)}`);
if (prop in target) {
return Reflect.get(target, prop, receiver);
}
return 'default';
},
// 拦截属性设置
set(target, prop, value, receiver) {
console.log(`SET ${String(prop)} = ${value}`);
if (prop === 'age' && typeof value !== 'number') {
throw new TypeError('age must be a number');
}
return Reflect.set(target, prop, value, receiver);
},
// 拦截 in 操作符
has(target, prop) {
console.log(`HAS ${String(prop)}`);
return prop in target;
},
// 拦截 delete
deleteProperty(target, prop) {
console.log(`DELETE ${String(prop)}`);
if (prop === 'name') {
throw new Error('Cannot delete name');
}
return Reflect.deleteProperty(target, prop);
},
// 拦截函数调用(当代理对象被当作函数调用时)
apply(target, thisArg, args) {
console.log(`CALL with ${args}`);
return Reflect.apply(target, thisArg, args);
},
// 拦截 new 操作符
construct(target, args) {
console.log(`NEW with ${args}`);
return Reflect.construct(target, args);
}
};
const proxy = new Proxy(target, handler);
console.log(proxy.name); // GET name → Alice
proxy.age = 31; // SET age = 31
// proxy.age = 'young'; // TypeError
console.log('age' in proxy); // HAS age → true
// delete proxy.name; // Error5.2 Proxy 实际应用
验证器:
function createValidator(target, schema) {
return new Proxy(target, {
set(obj, prop, value) {
const rule = schema[prop];
if (rule) {
if (rule.type && typeof value !== rule.type) {
throw new TypeError(`${prop} must be ${rule.type}`);
}
if (rule.min !== undefined && value < rule.min) {
throw new RangeError(`${prop} must be >= ${rule.min}`);
}
if (rule.max !== undefined && value > rule.max) {
throw new RangeError(`${prop} must be <= ${rule.max}`);
}
}
return Reflect.set(obj, prop, value);
}
});
}
const userSchema = { age: { type: 'number', min: 0, max: 150 } };
const user = createValidator({}, userSchema);
user.age = 25; // 正常
// user.age = -1; // RangeError
// user.age = 'old'; // TypeError日志/追踪:
function createLogger(target) {
return new Proxy(target, {
get(obj, prop) {
const value = Reflect.get(obj, prop);
if (typeof value === 'function') {
return function(...args) {
console.log(`[LOG] ${String(prop)} called with:`, args);
const result = value.apply(this, args);
console.log(`[LOG] ${String(prop)} returned:`, result);
return result;
};
}
return value;
}
});
}
const math = createLogger(Math);
// math.max(1, 2, 3); // 会有日志输出私有属性:
function createPrivateObject(obj, privateKeys = []) {
return new Proxy(obj, {
get(target, prop) {
if (privateKeys.includes(prop)) {
return undefined;
}
return Reflect.get(target, prop);
},
set(target, prop, value) {
if (privateKeys.includes(prop)) {
throw new Error(`Cannot set private property: ${String(prop)}`);
}
return Reflect.set(target, prop, value);
},
has(target, prop) {
if (privateKeys.includes(prop)) return false;
return Reflect.has(target, prop);
},
ownKeys(target) {
return Reflect.ownKeys(target).filter(k => !privateKeys.includes(k));
}
});
}Vue 3 响应式原理简介:
// 极简版 Vue 3 响应式系统
function reactive(target) {
const handlers = new Map();
let activeEffect = null;
function track(target, key) {
if (!activeEffect) return;
let depsMap = handlers.get(target);
if (!depsMap) {
depsMap = new Map();
handlers.set(target, depsMap);
}
let deps = depsMap.get(key);
if (!deps) {
deps = new Set();
depsMap.set(key, deps);
}
deps.add(activeEffect);
}
function trigger(target, key) {
const depsMap = handlers.get(target);
if (!depsMap) return;
const deps = depsMap.get(key);
if (deps) {
deps.forEach(effect => effect());
}
}
function effect(fn) {
activeEffect = fn;
fn();
activeEffect = null;
}
const proxy = new Proxy(target, {
get(obj, key, receiver) {
track(target, key);
return Reflect.get(obj, key, receiver);
},
set(obj, key, value, receiver) {
const oldValue = obj[key];
const result = Reflect.set(obj, key, value, receiver);
if (oldValue !== value) {
trigger(target, key);
}
return result;
}
});
return { proxy, effect };
}
// 使用
// const { proxy, effect } = reactive({ count: 0 });
// effect(() => console.log(`Count is: ${proxy.count}`));
// proxy.count++; // 控制台自动输出 "Count is: 1"5.3 Reflect 静态方法
Reflect 提供了与 Proxy 拦截器一一对应的静态方法,是函数式替代命令式操作的最佳实践。
const obj = { a: 1, b: 2 };
// Reflect.get — 替代 obj.prop
console.log(Reflect.get(obj, 'a')); // 1
// Reflect.set — 替代 obj.prop = value
Reflect.set(obj, 'c', 3);
console.log(obj.c); // 3
// Reflect.has — 替代 'prop' in obj
console.log(Reflect.has(obj, 'a')); // true
// Reflect.deleteProperty — 替代 delete obj.prop
Reflect.deleteProperty(obj, 'b');
console.log('b' in obj); // false
// Reflect.ownKeys — 替代 Object.getOwnPropertyNames + Object.getOwnPropertySymbols
console.log(Reflect.ownKeys(obj)); // ['a', 'c']
// Reflect.defineProperty — 替代 Object.defineProperty
Reflect.defineProperty(obj, 'd', { value: 4, writable: false });
// Reflect.construct — 替代 new 操作符
class Person {
constructor(name) { this.name = name; }
}
const p = Reflect.construct(Person, ['Alice']);
console.log(p instanceof Person); // true
// Reflect.apply — 替代 Function.prototype.apply
const nums = [1, 2, 3];
console.log(Reflect.apply(Math.max, null, nums)); // 3
// Reflect.getPrototypeOf / Reflect.setPrototypeOf
console.log(Reflect.getPrototypeOf(p) === Person.prototype); // true六、ES2016 - ES2024 年度新特性
6.1 ES2016
Array.prototype.includes:判断数组是否包含某个元素,比 indexOf 语义更清晰,能正确处理 NaN。
const arr = [1, 2, NaN, 4];
console.log(arr.includes(2)); // true
console.log(arr.includes(5)); // false
console.log(arr.includes(NaN)); // true(indexOf 不能)
// indexOf 的局限
console.log(arr.indexOf(NaN)); // -1(错误)指数运算符 `**:替代Math.pow`。
console.log(2 ** 3); // 8
console.log(2 ** 10); // 1024
console.log(2 ** -1); // 0.5
console.log(2 ** (1/2)); // 1.414...(平方根)
// 注意优先级
console.log(2 ** 3 ** 2); // 512(右结合,相当于 2 ** (3 ** 2))6.2 ES2017
async/await:基于 Promise 的异步语法糖,让异步代码看起来像同步代码。
// 基本用法
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function fetchData() {
console.log('开始获取数据...');
await delay(1000);
console.log('数据获取完成');
return { id: 1, name: 'Alice' };
}
async function main() {
try {
const data = await fetchData();
console.log(data);
} catch (err) {
console.error('错误:', err);
}
}
main();
// 并行请求
async function parallelFetch() {
const [user, posts] = await Promise.all([
fetch('/api/user').then(r => r.json()),
fetch('/api/posts').then(r => r.json())
]);
return { user, posts };
}
// 串行 vs 并行
async function demo() {
// 串行(耗时 = a + b)
const a = await delay(1000).then(() => 'A');
const b = await delay(1000).then(() => 'B');
// 并行(耗时 = max(a, b))
const [c, d] = await Promise.all([
delay(1000).then(() => 'C'),
delay(1000).then(() => 'D')
]);
}Object.values / Object.entries:
const obj = { a: 1, b: 2, c: 3 };
// ES5
console.log(Object.keys(obj)); // ['a', 'b', 'c']
// ES2017
console.log(Object.values(obj)); // [1, 2, 3]
console.log(Object.entries(obj)); // [['a', 1], ['b', 2], ['c', 3]]
// 实用组合
const entries = Object.entries(obj)
.filter(([_, v]) => v > 1)
.map(([k, v]) => ({ [k]: v * 2 }));
console.log(entries); // [{ b: 4 }, { c: 6 }]字符串填充:padStart / padEnd。
// padStart
console.log('5'.padStart(3, '0')); // '005'
console.log('12345'.padStart(3, '0')); // '12345'(长度不足不截断)
console.log('abc'.padStart(8, '123')); // '12312abc'
// padEnd
console.log('5'.padEnd(3, '0')); // '500'
console.log('hello'.padEnd(8, '!')); // 'hello!!!'
// 实用:格式化时间
const hour = String(3).padStart(2, '0');
const min = String(5).padStart(2, '0');
console.log(`${hour}:${min}`); // '03:05'6.3 ES2018
异步迭代器(Async Iterator):Symbol.asyncIterator 与 for await...of。
// 异步生成器
async function* asyncNumberGenerator() {
let i = 0;
while (i < 3) {
await new Promise(r => setTimeout(r, 100));
yield i++;
}
}
async function consumeAsyncIterator() {
for await (const num of asyncNumberGenerator()) {
console.log(num); // 0, 1, 2(每隔约 100ms 输出一个)
}
}
// 实际应用:分页 API
async function* paginate(url, maxPages = 5) {
let page = 1;
while (page <= maxPages) {
const response = await fetch(`${url}?page=${page}`);
const data = await response.json();
yield data.items;
if (!data.hasMore) break;
page++;
}
}
// async function loadAll() {
// for await (const items of paginate('/api/users')) {
// console.log('一批数据:', items);
// }
// }Promise.finally:无论 Promise 成功还是失败,最终都会执行的回调。
function showLoading(show) { /* ... */ }
function fetchUsers() {
showLoading(true);
return fetch('/api/users')
.then(res => res.json())
.catch(err => console.error(err))
.finally(() => showLoading(false)); // 无论成功失败,关闭加载状态
}
// 适合:loading 状态、释放资源、关闭连接对象展开(Rest/Spread Properties):
// 对象展开(前面已介绍)
const obj1 = { a: 1, b: 2 };
const obj2 = { ...obj1, c: 3 }; // { a: 1, b: 2, c: 3 }
// 对象剩余模式
const { a, ...rest } = obj2;
console.log(a); // 1
console.log(rest); // { b: 2, c: 3 }
// 合并与覆写
const defaults = { method: 'GET', headers: {}, timeout: 5000 };
const override = { method: 'POST', headers: { 'Content-Type': 'application/json' } };
const config = { ...defaults, ...override };
// { method: 'POST', headers: { 'Content-Type': 'application/json' }, timeout: 5000 }正则表达式 s 标志(dotAll):让 . 匹配包括换行符在内的所有字符。
console.log(/hello.world/.test('hello\nworld')); // false
console.log(/hello.world/s.test('hello\nworld')); // true
// 命名捕获组
const regex = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const match = regex.exec('2024-09-01');
console.log(match.groups);
// { year: '2024', month: '09', day: '01' }6.4 ES2019
Array.flat / flatMap:
const nested = [1, [2, [3, [4]]]];
// 默认展平一层
console.log(nested.flat()); // [1, 2, [3, [4]]]
// 指定展平层数
console.log(nested.flat(2)); // [1, 2, 3, [4]]
console.log(nested.flat(Infinity)); // [1, 2, 3, 4]
// flatMap = map + flat(1)
const arr = ['hello world', 'foo bar'];
console.log(arr.flatMap(s => s.split(' ')));
// ['hello', 'world', 'foo', 'bar']
// 比 map + flat 更高效Object.fromEntries:Object.entries 的逆操作。
const entries = [['a', 1], ['b', 2], ['c', 3]];
const obj = Object.fromEntries(entries);
console.log(obj); // { a: 1, b: 2, c: 3 }
// 从 Map 转对象
const map = new Map([['x', 10], ['y', 20]]);
const fromMap = Object.fromEntries(map);
console.log(fromMap); // { x: 10, y: 20 }
// 实用:过滤对象
const data = { a: 1, b: 2, c: 3, d: 4 };
const filtered = Object.fromEntries(
Object.entries(data).filter(([_, v]) => v > 2)
);
console.log(filtered); // { c: 3, d: 4 }catch 可选参数:
// ES2019 之前
try {
JSON.parse('invalid json');
} catch (err) {
console.error('解析失败');
}
// ES2019+ 可以省略括号和参数
try {
JSON.parse('invalid json');
} catch {
console.error('解析失败'); // 不关心具体错误对象
}
// 适合:错误信息已由上层处理、不需要错误详情String.trimStart / trimEnd:
const str = ' hello world ';
console.log(str.trimStart()); // 'hello world '
console.log(str.trimEnd()); // ' hello world'
console.log(str.trim()); // 'hello world'
// 别名:trimLeft / trimRight
console.log(str.trimLeft()); // 'hello world '6.5 ES2020
可选链操作符 ?.:安全访问深层嵌套属性,短路求值。
const user = {
name: 'Alice',
address: {
// street 不存在
city: 'Beijing'
},
getGreeting() {
return 'Hello';
}
};
// 传统写法(多层 &&)
const street = user && user.address && user.address.street;
// ES2020 可选链
const street2 = user?.address?.street;
console.log(street2); // undefined(不会报错)
// 可选函数调用
const greeting = user.getGreeting?.(); // 'Hello'
const missing = user.sayGoodbye?.(); // undefined
// 可选索引
const arr = [1, 2, 3];
console.log(arr?.[0]); // 1
console.log(arr?.[10]); // undefined(不会越界报错)
// 动态属性
const key = 'name';
console.log(user?.[key]); // 'Alice'
// 短路求值
const result = user?.nonexistent?.method();
// 如果 user?.nonexistent 是 undefined,不会尝试调用 method空值合并运算符 ??:当左侧为 null 或 undefined 时返回右侧值。
// || 的缺陷:会处理所有 falsy 值(0, '', false)
const count = 0;
console.log(count || 10); // 10(非预期)
console.log(count ?? 10); // 0(正确)
const name = '';
console.log(name || 'default'); // 'default'
console.log(name ?? 'default'); // ''
// ?? 只处理 null 和 undefined
const a = null;
const b = undefined;
const c = 0;
const d = false;
console.log(a ?? 'fallback'); // 'fallback'
console.log(b ?? 'fallback'); // 'fallback'
console.log(c ?? 'fallback'); // 0
console.log(d ?? 'fallback'); // false
// 与可选链结合
const config = {};
const port = config.server?.port ?? 3000;
console.log(port); // 3000
// 不能与 && 或 || 直接混合(需加括号)
// console.log(null || undefined ?? 'fallback'); // SyntaxError
console.log((null || undefined) ?? 'fallback'); // 'fallback'Promise.allSettled:等待所有 Promise 完成(无论成功或失败)。
const promises = [
Promise.resolve(1),
Promise.reject('error'),
Promise.resolve(3)
];
Promise.allSettled(promises).then(results => {
console.log(results);
// [
// { status: 'fulfilled', value: 1 },
// { status: 'rejected', reason: 'error' },
// { status: 'fulfilled', value: 3 }
// ]
});
// 对比 Promise.all:任意一个失败就整体失败
// Promise.all 适合「全要或全不要」
// Promise.allSettled 适合「各算各的」String.matchAll:返回所有匹配的迭代器。
const str = '2024-01-15, 2024-03-20, 2024-07-01';
const regex = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/g;
// ES2020
const matches = str.matchAll(regex);
for (const match of matches) {
console.log(match[0], match.groups);
// '2024-01-15' { year: '2024', month: '01', day: '15' }
// '2024-03-20' { year: '2024', month: '03', day: '20' }
// '2024-07-01' { year: '2024', month: '07', day: '01' }
}
// 转为数组
const allMatches = [...str.matchAll(regex)];
console.log(allMatches.length); // 3globalThis:统一访问全局对象的方式。
// 浏览器中:window
// Node.js 中:global
// Web Worker 中:self
// ES2020 之前各环境不同
// 兼容写法:
const glob = typeof window !== 'undefined' ? window :
typeof global !== 'undefined' ? global : self;
// ES2020
console.log(globalThis);
// 直接使用
globalThis.setTimeout(() => console.log('hello'), 100);6.6 ES2021
String.replaceAll:替换字符串中所有匹配项。
const str = '2024-09-01';
// replace 只替换第一个(除非用全局正则)
console.log(str.replace('-', '/')); // '2024/09-01'
// ES2021
console.log(str.replaceAll('-', '/')); // '2024/09/01'
// replaceAll 支持字符串或正则(必须带 g 标志)
console.log(str.replaceAll(/-/g, '.')); // '2024.09.01'
// 错误:不带 g 标志的正则
// str.replaceAll(/-/, '.'); // TypeErrorPromise.any:任意一个 Promise 成功即返回,全部失败则抛出 AggregateError。
const promises = [
Promise.reject('Error 1'),
new Promise(resolve => setTimeout(() => resolve('慢速成功'), 500)),
Promise.reject('Error 2')
];
Promise.any(promises)
.then(result => console.log(result)) // '慢速成功'(500ms 后)
.catch(err => console.log(err));
// 全部失败
Promise.any([
Promise.reject('A'),
Promise.reject('B')
]).catch(err => {
console.log(err instanceof AggregateError); // true
console.log(err.errors); // ['A', 'B']
});
// Promise 组合对比
// Promise.all — 全部成功 || 快速失败
// Promise.allSettled — 全部完成(无论成败)
// Promise.race — 第一个完成(无论成败)
// Promise.any — 第一个成功 || 全部失败数字分隔符 _:提高大数字可读性。
const billion = 1_000_000_000;
console.log(billion); // 1000000000
const price = 99_99; // 9999
const hex = 0xFF_FF_FF; // 16777215
const binary = 0b1010_1010; // 170
const sci = 1_000_000_000_000; // 万亿
// 注意:不能放在开头、结尾或小数点两侧
// const invalid = _100; // SyntaxError
// const invalid2 = 100_; // SyntaxError逻辑赋值运算符:&&=、||=、??=。
let a = 0;
let b = 1;
let c = null;
// 逻辑与赋值(a 为 truthy 时才赋值)
a &&= 10; // a 是 0(falsy),不赋值 → a 仍然是 0
b &&= 20; // b 是 1(truthy),赋值 → b 变为 20
// 逻辑或赋值(a 为 falsy 时才赋值)
a ||= 100; // a 是 0(falsy),赋值 → a 变为 100
// 空值合并赋值(a 为 null/undefined 时才赋值)
c ??= 200; // c 是 null,赋值 → c 变为 200
// 等价于
// a || (a = 100);
// b && (b = 20);
// c ?? (c = 200);
// 典型应用:设置默认值
let options = { timeout: 3000 };
options.timeout ??= 5000; // 已存在,不覆盖
let config = {};
config.port ??= 8080; // 不存在,赋默认值6.7 ES2022
Array.at:支持正负索引访问数组元素。
const arr = [10, 20, 30, 40, 50];
// 正索引
console.log(arr.at(0)); // 10
console.log(arr.at(2)); // 30
// 负索引(从末尾开始)
console.log(arr.at(-1)); // 50
console.log(arr.at(-2)); // 40
// 对比传统方式
console.log(arr[arr.length - 1]); // 50(不够优雅)
console.log(arr.slice(-1)[0]); // 50(性能较差)
// 同样适用于 TypedArray 和 String
console.log('hello'.at(-1)); // 'o'顶级 await (Top-level await):在模块顶层直接使用 await。
// fetch-config.js
// ES2022 之前:必须包在 async 函数中
// async function loadConfig() {
// const res = await fetch('/config.json');
// return res.json();
// }
// ES2022:模块顶层直接 await
const response = await fetch('/config.json');
const config = await response.json();
export default config;
// 依赖配置的模块
// import config from './fetch-config.js';
// console.log(config.apiUrl);
// 注意:顶级 await 只在 ES 模块中有效(type="module" 或 .mjs)
// 会阻塞模块加载,但有条件地阻塞(依赖该模块的模块才会等待)Object.hasOwn:安全地检查对象自有属性。
const obj = Object.create(null); // 无原型的对象
obj.name = 'Alice';
// hasOwnProperty 可能被覆盖或不存在
// console.log(obj.hasOwnProperty('name')); // TypeError(无原型)
// ES2022
console.log(Object.hasOwn(obj, 'name')); // true
console.log(Object.hasOwn(obj, 'toString')); // false
// 对比传统方式
const regularObj = { a: 1 };
console.log(Object.hasOwn(regularObj, 'a')); // true
console.log(Object.prototype.hasOwnProperty.call(regularObj, 'a')); // trueerror.cause:为错误传递原因信息。
// 创建带有原因的错误
try {
try {
throw new Error('数据库连接失败');
} catch (dbError) {
throw new Error('获取用户数据失败', { cause: dbError });
}
} catch (err) {
console.log(err.message); // '获取用户数据失败'
console.log(err.cause); // Error: 数据库连接失败
console.log(err.cause.message); // '数据库连接失败'
}
// 实际应用:多层错误传递
async function fetchUserData(userId) {
try {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`, { cause: response });
}
return await response.json();
} catch (err) {
throw new Error(`获取用户 ${userId} 数据失败`, { cause: err });
}
}Class 字段增强:公共字段、私有字段、静态字段。
class Counter {
// 公共字段(实例属性声明,无需构造函数)
count = 0;
// 私有字段(# 前缀,真正的私有)
#privateValue = 100;
#secret = 'hidden';
// 静态公共字段
static maxCount = 1000;
// 静态私有字段
static #instanceCount = 0;
constructor(name) {
this.name = name; // 构造函数初始化
Counter.#instanceCount++;
}
// 私有方法
#incrementPrivate() {
this.#privateValue++;
}
increment() {
this.count++;
this.#incrementPrivate();
}
getPrivateValue() {
return this.#privateValue;
}
// 静态私有方法
static #validate(count) {
return count >= 0 && count <= Counter.maxCount;
}
static getInstanceCount() {
return Counter.#instanceCount;
}
}
const c = new Counter('c1');
c.increment();
console.log(c.count); // 1
console.log(c.getPrivateValue()); // 101
// console.log(c.#privateValue); // SyntaxError: Private field
// console.log(c.#secret); // SyntaxError静态代码块(Static initialization blocks):
class Database {
static connection;
static config;
// 静态代码块 — 在类初始化时执行
static {
try {
// 复杂的静态初始化逻辑
this.config = {
host: process.env.DB_HOST || 'localhost',
port: parseInt(process.env.DB_PORT) || 5432
};
this.connection = { connected: true, ...this.config };
} catch (err) {
console.error('数据库初始化失败', err);
this.connection = null;
}
}
static getConnection() {
return this.connection;
}
}6.8 ES2023
Array.findLast / findLastIndex:从数组末尾开始查找。
const arr = [1, 2, 3, 4, 5, 3, 2, 1];
// 查找最后一个大于 2 的元素
const lastLarge = arr.findLast(x => x > 2);
console.log(lastLarge); // 3(最后一个大于2的)
// 查找最后一个满足条件的索引
const lastLargeIndex = arr.findLastIndex(x => x > 2);
console.log(lastLargeIndex); // 5(最后一个大于2的索引)
// 对比 find(从头找)
const firstLarge = arr.find(x => x > 2);
console.log(firstLarge); // 3(第一个大于2的)
// 实际应用:获取数组中最后一个偶数
const lastEven = arr.findLast(x => x % 2 === 0);
console.log(lastEven); // 2Hashbang 语法:JavaScript 引擎可识别脚本第一行的 #! 注释。
#!/usr/bin/env node
// 上面的 hashbang 会被 JavaScript 引擎自动忽略
// 使得 .js 文件可以直接作为 shell 脚本执行
console.log('Hello from executable JS file!');
// 之前需要手动处理
// #!/usr/bin/env node
// 在某些环境中需要依赖工具忽略 hashbang6.9 ES2024
Array GroupBy / GroupByToMap:按条件分组数组。
const items = [
{ name: 'apple', category: 'fruit' },
{ name: 'carrot', category: 'vegetable' },
{ name: 'banana', category: 'fruit' },
{ name: 'broccoli', category: 'vegetable' },
];
// Object.groupBy — 返回普通对象
const grouped = Object.groupBy(items, item => item.category);
console.log(grouped);
// {
// fruit: [{ name: 'apple', category: 'fruit' }, { name: 'banana', category: 'fruit' }],
// vegetable: [{ name: 'carrot', category: 'vegetable' }, { name: 'broccoli', category: 'vegetable' }]
// }
// Map.groupBy — 返回 Map(适合非字符串键)
const numbers = [1, 2, 3, 4, 5, 6];
const parityGroups = Map.groupBy(numbers, n => n % 2 === 0 ? 'even' : 'odd');
console.log(parityGroups.get('even')); // [2, 4, 6]
console.log(parityGroups.get('odd')); // [1, 3, 5]
// 按属性长度分组
const words = ['hello', 'world', 'hi', 'js', 'typescript'];
const byLength = Object.groupBy(words, w => w.length);
console.log(byLength);
// { '2': ['hi', 'js'], '5': ['hello', 'world'], '10': ['typescript'] }Promise.withResolvers:在 Promise 外部 resolve/reject。
// ES2024 之前
function createDeferred() {
let resolve, reject;
const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
return { promise, resolve, reject };
}
// ES2024
const { promise, resolve, reject } = Promise.withResolvers();
// 可以在任意地方 resolve/reject
setTimeout(() => resolve('完成'), 1000);
const result = await promise;
console.log(result); // '完成'
// 实际应用:事件转 Promise
function waitForEvent(element, eventName) {
const { promise, resolve } = Promise.withResolvers();
element.addEventListener(eventName, resolve, { once: true });
return promise;
}正则表达式 v 标志:增强的 Unicode 属性转义和集合操作。
// v 标志启用 Unicode 属性增强
const regex = /\p{Emoji}/v;
console.log(regex.test('😀')); // true
console.log(regex.test('a')); // false
// 集合操作:差集、交集、并集
// 匹配 ASCII 字母但不包括元音
const consonant = /[\p{ASCII}--[aeiou]]/v;
console.log(consonant.test('b')); // true
console.log(consonant.test('a')); // false
// 交集
const hexDigit = /[\p{ASCII}&&\p{Hex_Digit}]/v;
console.log(hexDigit.test('A')); // true
console.log(hexDigit.test('Z')); // false(Z 不是十六进制字符)
// 注意:v 标志需要现代化引擎支持七、总结
从 ES2015 的里程碑式发布到 ES2024 的渐进增强,JavaScript 已经成长为一门功能完备、表达力极强的语言。下表概括了各版本的核心贡献:
| 版本 | 核心贡献 |
|---|---|
| ES2015 | 模块化、箭头函数、Promise、类、let/const、解构、模板字符串、Symbol、Set/Map、Proxy、迭代器/生成器 |
| ES2016 | Array.includes、指数运算符 |
| ES2017 | async/await、Object.values/entries、字符串填充 |
| ES2018 | 异步迭代器、Promise.finally、对象/正则展开 |
| ES2019 | flat/flatMap、Object.fromEntries、catch 可选 |
| ES2020 | 可选链、空值合并、Promise.allSettled、globalThis |
| ES2021 | replaceAll、Promise.any、数字分隔符、逻辑赋值 |
| ES2022 | Array.at、顶级 await、Object.hasOwn、class 字段、error.cause |
| ES2023 | findLast/findLastIndex、Hashbang |
| ES2024 | GroupBy、Promise.withResolvers、正则 v 标志 |
学习建议:
- 优先掌握 ES2015 核心:80% 的日常开发都围绕 ES2015 的语法展开
- 渐进式吸收:每年新特性通常只有 2-4 个,逐个理解并应用到项目中
- 关注 TC39 流程:Stage 3 及以上特性基本稳定,可提前了解
- 实践出真知:最好的学习方式是在实际项目中有意识地使用新特性
JavaScript 仍在快速演进,保持学习的热情,你将能用这门语言构建任何你想构建的东西。Happy coding!