ES6 是 2015 年发布的。
距离现在,已经过去整整十年。
这十年里,JavaScript 每一年都在进化。
新语法、新 API、新并发模型、新数据结构......
可大多数人,对 JavaScript 的认知,仍停留在:
- 箭头函数
- 解构赋值
- Promise
- let / const
从 ES2016 到 ES2025,你真的跟上了吗?
这篇文章,我会按时间线带你系统梳理 JavaScript 十年的演进轨迹。
ES2016 → ES2020
这5年新出的特性我想大多数人都已经熟练使用了,这里就简单列下,不详细介绍api细节了
ES2016
这是一个小版本,主要有以下3个特性:
-
Array.prototype.includes() -
指数运算符 (
**)js2 ** 3; // 8 -
幂赋值运算符
**=jslet num = 2; num **= 3; // num = num ** 3 console.log(num); // 8
ES2017
ES2017的重点是异步编程,对象操作
- async/await
- Object.values()/Object.entries()
- Object.getOwnPropertyDescriptors(): 返回对象所有自身属性的描述符对象
- 字符串填充String Padding
js
console.log('5'.padStart(3, '0')); // '005'
console.log('hello'.padEnd(10, '*')); // 'hello*****'
-
SharedArrayBuffer 和 Atomics
这两个用在web worker中。主线程和worker使用postMessage通信往往要将数据拷贝一份,SharedArrayBuffer 则允许 Worker 线程与主线程共享同一块内存,通过
postMessage将SharedArrayBuffer转移给 Worker,不会复制数据:js// main.js const sab = new SharedArrayBuffer(1024); const worker = new Worker('worker.js'); worker.postMessage(sab); //不复制数据js// worker.js self.onmessage = (e) => { const sab = e.data; // 同一个内存块 };
计算机中写操作可能被编译成多条指令,如果尚未写完就有其他线程读数据,便会产生错误。在多线程操作SharedArrayBuffer时就可能会出现这种问题。Atomics提供了原子级操作,其他线程读取到的数据,要么是没写入的,要么是已写完的。另外Atomics还提供了线程的阻塞和唤醒。
ES2018
ES2018新增了多个特性,算是一次中等规模升级,主要有异步编程的增强、对象和数组操作的改进、正则表达式的扩展,以及模板字面量的优化。
- 异步生成器/异步迭代器
js
async function* fetchPages() {
let page = 1;
while (page <= 3) {
const response = await fetch(`https://api.example.com/page/${page}`);
yield await response.json();
page++;
}
}
(async () => {
for await (const data of fetchPages()) {
console.log(data);
}
})();
Promise.prototype.finally()- rest/spreat操作符
... - 正则表达式增强(后行断言,命名捕获等)
- 模板字符串的标签模板提供raw访问原始字符串
js
function tag(strings) {
return strings.raw[0]; // 访问原始字符串,包括非法转义,比如LaTeX语法
}
console.log(tag`\u{00000042}`); // strings[0]是'B' strings.raw[0]为\u{00000042}
ES2019
该版本内容不多但很实用
- Array.prototype.flat() / flatMap()
Object.fromEntries()- String.trimStart() / trimEnd()
- Optional catch binding :
catch可省略错误参数
js
try {
JSON.parse('invalid json');
} catch {
console.log('Parsing failed'); // 无需未使用的 error 变量
}
- Symbol.description
- Function.prototype.toString()能返回函数精确源码,包括注释和空格,方便调试
- 稳定的 Array.prototype.sort()
ES2020
这个版本也是一个里程碑,更新了大量内容,而且都很实用
- BigInt
- Dynamic Import
import() - 空值合并运算符
?? - 可选链运算符
?. - Promise.allSettled()
- String.prototype.matchAll()
- 标准全局对象globalThis
- 模块命名空间导出(export * as ns from 'mod')
- for-in 枚举顺序与定义顺序一致
从ES2021开始新增的特性,在我日常code review中看到的越来越少了,但很多特性还是很实用的。
ES2021
String.prototype.replaceAll()
在此之前全局替换需要用正则/g标志
逻辑赋值运算符 (&&=, ||=, ??=)
js
let x = 0;
x ||= 10; // x = 10(因为 0 是 falsy)
let y = 5;
y &&= 20; // y = 20(因为 5 是 truthy)
let z;
z ??= 'default'; // z = 'default'(因为 undefined 是 nullish)
数字分隔符(1_000_000)
允许在数字字面量中使用下划线(_)作为分隔符,提高大数字的可读性。
Promise.any()
返回一个 Promise,在任意一个输入 Promise resolved 时解决;如果所有 rejected,则返回 AggregateError。
-
语法:Promise.any(iterable)
- iterable:Promise 数组或其他可迭代对象。
-
示例
jsconst promises = [ Promise.reject('Error 1'), Promise.resolve('Success'), Promise.reject('Error 2') ]; Promise.any(promises) .then(value => console.log(value)) // 'Success'(第一个 resolved) .catch(error => console.error(error)); // 如果全 reject:AggregateError
WeakRefs和FinalizersRegistry:
- WeakRefs 用于引用对象而不阻止垃圾回收
- FinalizersRegistry 用于缓存或观察对象,而不干扰内存管理;FinalizationRegistry 提供清理回调,但不保证时序。
js
let obj = { name: 'Alice' };
const weak = new WeakRef(obj);
const registry = new FinalizationRegistry(heldValue => {
console.log(`Object with ${heldValue} cleaned up`);
console.log(weak.deref());//undefined
});
registry.register(obj, 'Alice');
obj = null; // GC 时调用 callback
ES2022
Top-level await:
可以直接在模块最外层使用 await
Class的私有/静态成员和方法
增加了#标识私有,static标识静态(现在都用ts了,这两个特性很少用到)
js
class Counter {
#value = 0;
#increment() {
this.#value++;
}
get #count() {
return this.#value;
}
add() {
this.#increment();
}
getValue() {
return this.#count;
}
}
const c = new Counter();
c.add();
console.log(c.getValue()); // 1
// c.#increment(); // SyntaxError,不过控制台访问不会报错
js
class MathUtils {
static PI = 3.14159;
static #secret = 42;
static getPi() {
return this.PI;
}
static getSecret() {
return this.#secret;
}
}
console.log(MathUtils.PI); // 3.14159
console.log(MathUtils.getSecret()); // 42
Error.cause:
在 Error 对象中添加 cause 属性,用于链式记录错误上下文,方便调试。
js
try {
throw new Error('Original issue');
} catch (err) {
throw new Error('Failed operation', { cause: err });
}
// 结果错误:Failed operation (cause: Original issue)
at方法:
新增访问索引方法,可用于数组、字符串和 TypedArray
js
const arr = [1, 2, 3];
console.log(arr.at(1)); // 2
console.log(arr.at(-1)); // 3(最后一个元素,极力推荐这种写法)
console.log('hello'.at(-2)); // 'l'
Object.hasOwn():
代替Object.prototype.hasOwnProperty.call()
正则表达式的/d标志:
可用/d标志获取匹配范围
js
const match = 'hello world'.matchAll(/(hello)/dg);
for (const m of match) {
console.log(m.indices[0]); // [0, 5] 对于 'hello'
}
ES2023
Array 和 TypedArray的末尾查找
新增findLast() / findLastIndex()
通过拷贝修改数组(不改变原数组)
新增 toSorted() / toReversed() / toSpliced() / with()
-
语法:
- array.toReversed():返回反转拷贝。
- array.toSorted(compareFn):返回排序拷贝。
- array.toSpliced(start, deleteCount, ...items):返回拼接拷贝。
- array.with(index, value):返回替换指定索引值的拷贝。
-
示例:
jsconst arr = [1, 3, 2]; console.log(arr.toSorted()); // [1, 2, 3](原 arr 不变) console.log(arr.toReversed()); // [2, 3, 1] console.log(arr.toSpliced(1, 1, 4)); // [1, 4, 2] console.log(arr.with(0, 5)); // [5, 3, 2]
Hashbang 语法
允许在 ECMAScript 文件开头使用 #!(shebang)注释,指示解释器执行脚本。
js
#!/usr/bin/env node
console.log('Hello from Node.js');
允许 Symbols 用作 WeakMap、WeakSet 的键
此前key仅支持对象
ES2024
Promise.withResolvers()
同时创建 Promise 及其 resolve 和 reject 函数,便于手动控制 Promise 的状态。
js
const { promise, resolve, reject } = Promise.withResolvers();
setTimeout(() => resolve('Success'), 1000);
promise.then(value => console.log(value)); // 'Success'
Object.groupBy() 和 Map.groupBy()
静态方法,用于根据回调函数返回的值对可迭代对象进行分组,返回一个对象(Object.groupBy)或 Map(Map.groupBy)。
-
语法:
- Object.groupBy(iterable, callback)
- Map.groupBy(iterable, callback)
-
示例:
jsconst items = [ { name: 'apple', type: 'fruit' }, { name: 'carrot', type: 'vegetable' }, { name: 'banana', type: 'fruit' } ]; const grouped = Object.groupBy(items, item => item.type); console.log(grouped); // { fruit: [{...}, {...}], vegetable: [{...}] } const groupedMap = Map.groupBy(items, item => item.type); console.log(groupedMap.get('fruit')); // [{...}, {...}]
正则表达式/v标志
新标志 /v 启用 Unicode 集模式,支持属性的组合、范围、否定、交集 / 并集运算
在 /v 标志出现前,JS 正则有 /u 标志支持基础 Unicode 属性转义 (如 \p{Letter} 匹配字母),但只能匹配 "单一属性",无法直接表达 "属性的组合 / 范围 / 否定",而 /v 标志正是为了解决这个问题,提供扩展 Unicode 属性转义能力。
-
语法:/pattern/v
-
示例:
jsconst regex = /[\p{Emoji}&&\p{Emoji_Presentation}]/v; console.log(regex.test('😀')); // true(Emoji) // 集操作示例 const setDiff = /[a-z--[aeiou]]/v; // 辅音 console.log(setDiff.test('b')); // true
Atomics.waitAsync()
共享内存的异步等待方法,返回一个 Promise,在共享值变化时解决。
-
语法:Atomics.waitAsync(array, index, value, timeout)
-
示例:
jsconst sab = new SharedArrayBuffer(16); const int32 = new Int32Array(sab); const result = Atomics.waitAsync(int32, 0, 0); result.value.then(() => console.log('Woken up')); // 其他线程:Atomics.store(int32, 0, 1); Atomics.notify(int32, 0);
ArrayBuffer 和 SharedArrayBuffer 的resize和transfer
-
语法:
- buffer.resize(newLength)
- buffer.transfer(newLength):返回新缓冲区,旧的被分离。
- 类似方法可用于 SharedArrayBuffer。
-
示例:
jsconst buffer = new ArrayBuffer(8, { maxByteLength: 16 }); buffer.resize(16); console.log(buffer.byteLength); // 16 const transferred = buffer.transfer(); // 原 buffer 被分离,无法使用
String.prototype.isWellFormed() 和 toWellFormed()
这两个api用于"格式不良"字符串.
JavaScript 字符串基于 UTF-16 编码,其中单独的代理对字符(未配对的高 / 低代理项) 属于 "格式不良" 的字符串(也叫 "畸形 UTF-16 字符串")。
高代理项范围:0xD800 - 0xDBFF
低代理项范围:0xDC00 - 0xDFFF只有高 + 低代理项配对才是合法的 UTF-16 字符,单独出现其中一个就是 "格式不良"。
js
// 1. 格式良好的字符串(正常字符、合法代理对)
const validStr1 = 'Hello 世界';
console.log(validStr1.isWellFormed()); // true
const validStr2 = '\uD83D\uDE00'; // 😀(合法的高+低代理对)
console.log(validStr2.isWellFormed()); // true
// 2. 格式不良的字符串(单独的高代理项/低代理项)
const invalidStr1 = '\uD83D'; // 单独的高代理项(无对应低代理项)
console.log(invalidStr1.isWellFormed()); // false
const invalidStr2 = '测试\uDC00'; // 单独的低代理项(无对应高代理项)
console.log(invalidStr2.isWellFormed()); // false
// 3. 格式良好的字符串:返回原字符串副本
const validStr = 'Hello 😀';
console.log(validStr.toWellFormed()); // Hello 😀
console.log(validStr.toWellFormed() === validStr); // true(内容相同,引用不同)
// 4. 格式不良的字符串:替换未配对代理项为 �
const invalidStr1 = '\uD83D'; // 单独高代理项
console.log(invalidStr1.toWellFormed()); // �
ES2025
迭代器助手方法(Iterator Helpers)
引入 Iterator 全局对象及其原型方法,支持对任何可迭代对象(如 Array、Set、Map)进行函数式操作,如 map、filter 等。这些方法返回新的迭代器,支持惰性求值。
-
语法:Iterator.from(iterable).method(callback)
- 支持方法:map()、filter()、reduce()、take()、drop()、flatMap()、toArray()、forEach() 等。
-
示例:
jsconst arr = [1, 2, 3, 4]; const evenSquares = Iterator.from(arr) .filter(x => x % 2 === 0) .map(x => x ** 2) .toArray(); console.log(evenSquares); // [4, 16]
Set新增方法
为 Set.prototype 添加数学集合操作方法,支持集合的并集、交集、差集等。
-
新增方法:
- set.union(other) 并集
- set.intersection(other) 交集
- set.difference(other) 差集
- set.symmetricDifference(other) 对称差集(并集减交集)
- set.isSubsetOf(other) 子集
- set.isSupersetOf(other) 超集
- set.isDisjointFrom(other) 不相交
-
示例:
jsconst setA = new Set([1, 2, 3]); const setB = new Set([2, 3, 4]); console.log(setA.union(setB)); // Set {1, 2, 3, 4} console.log(setA.intersection(setB)); // Set {2, 3} console.log(setA.isSubsetOf(setB)); // false
直接导入JSON 模块
引入导入属性(with 关键字),支持直接导入 JSON 文件作为模块。
-
语法:import json from "./data.json" with { type: "json" };
-
示例:
jsimport data from "./config.json" with { type: "json" }; console.log(data); // { key: "value" }
Promise.try()
一个新的静态方法,用于包装函数调用,确保返回 Promise,无论函数是否异步或抛出错误。
-
语法:Promise.try(callback)
-
示例:
jsPromise.try(() => { if (Math.random() > 0.5) throw new Error('Fail'); return 'Success'; }) .then(result => console.log(result)) .catch(error => console.error(error));
新增Float16Array
引入 Float16Array 类型数组,支持 16 位浮点数,以及 DataView 的 getFloat16/setFloat16 和 Math.f16round()。
RegExp.escape() 方法
一个静态方法,用于转义字符串,使其可安全用于正则表达式。
-
语法:RegExp.escape(str)
-
示例:
jsconst userInput = 'a.b*c?'; const regex = new RegExp(RegExp.escape(userInput), 'g'); console.log('a.b*c?'.replace(regex, 'replaced')); // 'replaced'
正则表达式内联标志
-
语法:/(?i:case-insensitive)/
-
示例:
jsconst regex = /(?i:hello)/; console.log(regex.test('HELLO')); // true(忽略大小写)
正则表达式重复命名捕获组
允许在正则表达式中重复使用相同的命名捕获组名称。
-
语法:/(?<group>a)|(?<group>b)/
-
示例:
jsconst regex = /(?<year>\d{4})-(?<month>\d{2})|(?<year>\d{4})/(?<month>\d{2})/; const match = '2025-07'.match(regex); console.log(match.groups.year); // '2025' console.log(match.groups.month); // '07'