从 ES2015 到 ES2026:JavaScript 新特性概览
Category(分类): JavaScript Status: 已更新
原文标题为“1.5 万字概括 ES6 全部特性(已更新 ES2020)”。本文保留原文的知识地图和学习顺序,并补充 ES2021~ES2026 的标准变化。
ECMAScript 是 JavaScript 的语言标准。ES2015 是 ECMAScript 的第 6 个版本,因此“ES6”既可以指 ES2015,也常被用作 ES2015 及之后一批语法改进的历史称呼。2015 年之后,标准按年份发布:ES2016、ES2017……而不是把所有新版本都继续叫作 ES6。
截至本文更新时,最新发布的语言标准是 ECMAScript 2026(ECMA-262 第 17 版,2026 年 6 月发布)。浏览器、Node.js、Bun、Deno 和构建工具对不同特性的支持进度并不完全一致,使用前应查看目标运行环境和兼容性数据。

图中的“ES6”是历史称呼,图中只覆盖到 ES2020,不能作为当前标准的完整清单。
一、ES2015(ES6)核心特性
1. let、const 和块级作用域
let count = 0
const name = 'Ada'
if (true) {
let count = 1
const message = `${name}: ${count}`
console.log(message)
}
console.log(count) // 0
var是函数作用域;在顶层脚本中声明时还会和全局对象产生特定关系,模块顶层则不是简单的window属性;let和const是块级作用域;const只能保证绑定不能重新赋值,不能冻结对象内部:
const user = { name: 'Ada' }
user.name = 'Grace' // 可以修改对象属性
// user = {} // TypeError,不能重新绑定
let、const、class和模块导入绑定在声明前处于暂时性死区(TDZ);它们不是“完全没有提升”,而是绑定已经创建但在初始化前不能访问;- 同一作用域不能重复声明同名的
let、const或class; const声明时必须初始化;for、for…of和for…in中的let每轮循环可以拥有独立绑定,适合异步回调引用当前迭代值。
2. 解构赋值
解构按照数组的可迭代协议或对象属性名取值:
const [first, second = 'default'] = ['a']
const { name: userName, age = 18 } = { name: 'Ada' }
function createUser({ name, role = 'user' } = {}) {
return { name, role }
}
let [x, y] = [1, 2]
;[x, y] = [y, x]
常见形式:
- 数组解构:按迭代顺序取值,支持剩余项;
- 对象解构:按属性名取值,可改名、设置默认值;
- 参数解构:可以和参数默认值结合;
- 字符串可以按迭代器解构;
Set、Map等可迭代对象可以使用数组模式;- 数值和布尔值会被包装后读取属性,因此
const { toString } = 123可以工作; null和undefined不能被解构为对象,会抛出TypeError。
默认值只在对应值严格等于 undefined 时生效,null 不会触发默认值:
const { a = 1, b = 2 } = { a: undefined, b: null }
console.log(a, b) // 1 null
3. 字符串扩展与 Unicode
JavaScript 字符串按 UTF-16 码元表示。部分 Unicode 码点会使用两个码元(代理对),所以 str.length 不总是“用户看到的字符数”。for…of、展开运算符和 Array.from() 能按 Unicode 码点迭代,但复杂表情、组合字符和某些书写系统的“用户感知字符”还需要 Intl.Segmenter。
const text = '😀'
console.log(text.length) // 2,UTF-16 码元数量
console.log([...text].length) // 1,Unicode 码点数量
console.log(text.codePointAt(0))
console.log(String.fromCodePoint(0x1f600))
ES2015 常用 API:
- 模板字符串:支持多行和插值;
- 标签模板:把模板片段和插值作为参数传给函数;
String.raw():读取模板字面量的原始字符串片段,不负责 HTML 转义;includes()、startsWith()、endsWith();repeat();padStart()、padEnd()(ES2017);trimStart()、trimEnd()(ES2019);matchAll()(ES2020):返回全局正则匹配结果的迭代器;normalize():对 Unicode 等价表示进行正规化,但不等于语言学上的拼写比较。
const user = 'Ada'
const greeting = `Hello, ${user}!`
const raw = String.raw`line\n${user}`
console.log(raw) // line\nAda
4. 数值扩展
const binary = 0b1010
const octal = 0o755
console.log(Number.isFinite(10))
console.log(Number.isNaN(Number.NaN))
console.log(Number.isInteger(10))
console.log(Number.isSafeInteger(2 ** 53 - 1))
常用 API:
Number.EPSILON是1与下一个可表示的Number之间的间隔,不是所有计算的“最小精度”;Number.MAX_SAFE_INTEGER是2 ** 53 - 1;Number.MIN_SAFE_INTEGER是-(2 ** 53 - 1);Number.isFinite()、Number.isNaN()不会像全局isFinite()、isNaN()那样先进行宽松转换;Number.isInteger()、Number.isSafeInteger();Number.parseInt()、Number.parseFloat()是全局函数的规范化挂载,解析时仍应明确基数和输入格式;Math.trunc()、Math.sign()、Math.cbrt()、Math.clz32()、Math.imul()、Math.fround()、Math.hypot();Math.expm1()、Math.log1p()、Math.log10()、Math.log2()以及双曲函数。
Math.sign() 对 NaN 返回 NaN,对 -0 返回 -0,不能简单概括成只有 -1/0/1。
5. 对象扩展
const key = 'score'
const score = 100
const user = {
name: 'Ada',
score,
[key]: score,
greet() {
return `Hi, ${this.name}`
}
}
ES2015 常用能力:
- 属性简写和方法简写;
- 计算属性名;
super:在对象方法中访问原型,在类方法中访问父类原型或父类构造函数;Object.is():与===类似,但把NaN视为自身相等,并区分+0与-0;Object.assign():把一个或多个源对象的可枚举自有字符串/Symbol 属性浅复制到目标对象并返回目标对象;Object.getPrototypeOf()、Object.setPrototypeOf();Object.getOwnPropertyDescriptors()(ES2017);Object.values()、Object.entries()(ES2017);Object.fromEntries()(ES2019);Object.hasOwn()(ES2022)是比obj.hasOwnProperty(key)更稳妥的自有属性检查方式。
__proto__ 是历史兼容的访问器,不建议作为通用 API;对象字面量中的 __proto__ 还有特殊语法行为。更清晰的代码应使用 Object.getPrototypeOf()、Object.setPrototypeOf() 或 Object.create()。
对象属性遍历的大致顺序是:整数索引键按升序,其余字符串键按创建顺序,Symbol 键按创建顺序。常用方法区别如下:
| 方法 | 自有属性 | 继承属性 | 可枚举 | Symbol |
|---|---|---|---|---|
for…in | 是 | 是 | 字符串属性 | 否 |
Object.keys() | 是 | 否 | 字符串属性 | 否 |
Object.getOwnPropertyNames() | 是 | 否 | 包括不可枚举 | 否 |
Object.getOwnPropertySymbols() | 是 | 否 | 包括不可枚举 | 仅 Symbol |
Reflect.ownKeys() | 是 | 否 | 包括不可枚举 | 是 |
6. 数组扩展
const source = [1, 2, 3]
const copy = [...source]
const merged = [...source, 4, 5]
const max = Math.max(...source)
const arrayLike = { 0: 'a', 1: 'b', length: 2 }
const values = Array.from(arrayLike)
const setValues = Array.from(new Set([1, 1, 2]))
- 展开语法可以展开可迭代对象到数组字面量、函数参数或对象字面量;
- 剩余参数/剩余属性是收集操作,不能把 rest 和 spread 当作同一个语法;
Array.from()接收可迭代对象或类数组对象,可传映射函数;Array.of()避免Array(7)被解释为长度为 7 的稀疏数组;copyWithin()原地浅复制,不改变数组长度;find()、findIndex()(ES2015);findLast()、findLastIndex()(ES2023);fill()原地填充;keys()、values()、entries()返回迭代器;- 数组空位不是普通的
undefined元素,不同数组方法对空位的处理不同,业务代码应尽量避免依赖稀疏数组。
const sparse = Array(3)
console.log(0 in sparse) // false
console.log(sparse.map(() => 1)) // 仍然是稀疏数组
console.log([...sparse]) // [undefined, undefined, undefined]
7. 函数扩展
默认参数与剩余参数
function connect(host = 'localhost', port = 80) {
return `${host}:${port}`
}
function sum(first, ...rest) {
return rest.reduce((total, value) => total + value, first)
}
默认参数可以出现在普通参数之前或中间,例如 function f(a = 1, b) {} 是合法语法;剩余参数必须是最后一个参数。使用默认参数、解构参数或剩余参数的函数有独立的参数环境,不能简单套用非默认参数函数与 arguments 的映射关系。
函数的 length 只统计第一个默认参数之前的普通参数,不包括剩余参数。
箭头函数
const double = value => value * 2
const pickName = ({ name }) => name
const makeUser = name => ({ name })
箭头函数:
- 没有自己的
this,使用词法上的外层this; - 没有自己的
arguments,可以使用剩余参数; - 没有
prototype,不能用new调用; - 不能定义
yield; - 适合回调,但不适合作为需要动态
this的对象方法或构造函数。
尾调用
ES2015 规范描述了严格模式下的 Proper Tail Calls,但主流 JavaScript 引擎并未普遍实现为可依赖的优化。因此“尾递归一定不会栈溢出”是不正确的。需要深度递归时应使用循环、显式栈或确认目标引擎的行为。
8. RegExp 扩展
u:按 Unicode 模式解析正则,配合码点转义和 Unicode 字符处理;y:粘连匹配,必须从lastIndex指定的位置开始匹配;它不是简单等同于固定的^;s(ES2018):让点号匹配换行符;d(ES2022):返回匹配组的索引范围;g、i、m、s、u、y可通过regex.flags查询;dotAll、unicode、sticky、hasIndices等属性反映对应模式;- 命名捕获组:
/(?<year>\d{4})/; - Unicode 属性转义:
/\p{Script=Greek}/u; - 后行断言和后行否定断言(ES2018):
/(?<=USD)\d+/、/(?<!USD)\d+/; RegExp.escape()(ES2025)用于安全地转义要放入正则的字面字符串。
const date = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/d
const match = date.exec('2025-06-01')
console.log(match?.groups, match?.indices?.groups)
9. Symbol
Symbol() 创建唯一的原始值,参数只是描述:
const first = Symbol('id')
const second = Symbol('id')
console.log(first === second) // false
const sharedA = Symbol.for('app.id')
const sharedB = Symbol.for('app.id')
console.log(sharedA === sharedB) // true
Symbol()不需要也不能使用new;Symbol.for()使用 ECMAScript 的全局 Symbol 注册表;同一 JavaScript agent 中的多个 realm 可以共享它,但不同 Worker、进程或独立运行时之间不应假设共享;Symbol.keyFor()只能查询通过Symbol.for()注册的 Symbol;- Symbol 作为对象属性时是公开属性,不是安全的私有字段;
String(symbol)可以显式转换,'' + symbol会抛出TypeError;Object.getOwnPropertySymbols()和Reflect.ownKeys()可以发现 Symbol 属性;- 内置 well-known symbols 包括
Symbol.iterator、Symbol.toPrimitive、Symbol.toStringTag、Symbol.species、Symbol.hasInstance、Symbol.match、Symbol.replace、Symbol.search、Symbol.split和Symbol.isConcatSpreadable。
10. Set、Map、WeakSet、WeakMap
Set
Set 保存唯一值,按插入顺序迭代,比较采用 SameValueZero,因此多个 NaN 只保留一个,+0 和 -0 视为同一个值。
const set = new Set([1, 2, 2, Number.NaN])
set.add(3).delete(1)
console.log(set.has(2), set.size)
Map
Map 的键可以是任意值,包括对象、函数、Symbol 和 NaN:
const cache = new Map()
const key = { id: 1 }
cache.set(key, { result: 'ok' })
console.log(cache.get(key))
对象键使用引用身份比较;两个内容相同但引用不同的对象是两个不同的键。Map 和 Set 都按插入顺序迭代,提供 keys()、values()、entries() 和 forEach()。
WeakSet 与 WeakMap
Weak 集合不能枚举。现代标准允许非注册 Symbol作为 WeakMap 的键和 WeakSet 的值,但通过 Symbol.for() 创建的注册 Symbol 不允许;旧运行环境可能尚未支持该扩展。弱引用不阻止垃圾回收,但不应把它理解成“马上自动删除”或用来观察 GC 时机。
WeakMap的键是弱引用,值仍然是普通强引用;- Weak 集合适合把元数据绑定到对象,而不希望集合延长对象生命周期;
- 不能依赖 Weak 集合实现可观察的缓存大小、遍历或可靠清理通知;
FinalizationRegistry也不能作为业务正确性、资源及时释放或事务逻辑的依据。
11. Proxy 与 Reflect
Proxy 为目标对象创建代理,拦截读取、设置、调用、构造和反射操作:
const user = { name: 'Ada' }
const observed = new Proxy(user, {
get(target, property, receiver) {
console.log('读取', String(property))
return Reflect.get(target, property, receiver)
},
set(target, property, value, receiver) {
if (property === 'age' && !Number.isInteger(value)) {
throw new TypeError('age 必须是整数')
}
return Reflect.set(target, property, value, receiver)
}
})
observed.name
observed.age = 30
常见 trap:get、set、has、deleteProperty、defineProperty、ownKeys、getOwnPropertyDescriptor、getPrototypeOf、setPrototypeOf、isExtensible、preventExtensions、apply、construct。
注意:
- 必须通过代理引用操作,直接操作目标对象不会触发 trap;
- trap 需要遵守不可配置、不可写等属性不变量,否则会抛错;
Reflect方法提供与内部对象操作接近的返回值语义,常用于在 trap 中正确转发操作;Object.defineProperty()等并没有因为Reflect出现就整体“废弃”;- 代理会影响性能、调试和身份比较,不能为了“响应式”给所有对象无差别套代理;
- 当方法通过代理读取后调用时,方法中的
this可能是代理;需要访问原对象时要明确设计。
Proxy.revocable() 返回 { proxy, revoke },调用 revoke() 后代理失效。
12. Class、继承和私有字段
类是基于原型的构造机制的语法形式,不是完全不同于原型的对象模型:
class Person {
static species = 'human'
#name
constructor(name) {
this.#name = name
}
greet() {
return `Hello, ${this.#name}`
}
static create(name) {
return new this(name)
}
}
class Student extends Person {
constructor(name, school) {
super(name)
this.school = school
}
}
- 类方法默认不可枚举,定义在
prototype上; static成员属于类,不属于实例;静态字段和静态初始化块是 ES2022 能力;- 私有字段、私有方法和私有访问器使用
#,只能在类体内部按语法访问; #field in object可以检查对象是否具有该私有品牌;- 派生类构造函数必须先调用
super(),之后才能使用this; super()负责调用父类构造函数并初始化派生实例,不是简单的“先把父类实例属性复制到 this”;super.method()通过父类原型查找方法,super.staticMethod()在静态方法中通过父类构造函数查找;- 类声明不会像函数声明那样在使用前可访问,仍处于 TDZ;
new.target可以判断构造调用链中的实际构造目标。
ES2022 的私有字段已经标准化;装饰器仍应依据目标环境和 TC39 进度判断,不能把 Babel/TypeScript 装饰器代码直接称为所有浏览器都支持的 ES2020 语法。
13. Module:ESM
ES 模块使用静态的 import/export:
// math.js
export const pi = 3.14159
export function area(radius) {
return pi * radius ** 2
}
export default { version: 1 }
// app.js
import config, { area as circleArea } from './math.js'
console.log(config.version, circleArea(2))
- 模块拥有自己的顶层作用域;
- 模块代码默认采用严格模式;
- 导出的是 live binding,导入方不能重新赋值,但能观察导出模块对绑定的后续更新;
- 模块只会在同一模块图中求值一次,但模块命名空间对象不是普通可变对象;
export *不会自动转发默认导出;import()是返回 Promise 的动态导入,可用于按需加载和条件加载;import.meta提供模块元信息,例如import.meta.url;- 顶层
await是 ES2022 能力,只能用于模块;它会让依赖该模块的模块等待异步求值; - JSON/CSS 等模块和 import attributes 的支持依赖运行环境,语法常见形式是
with { type: 'json' },不要把 Node、浏览器和打包器行为混为一谈。
浏览器中通常通过 <script type="module"> 加载模块,它具有延迟执行语义并支持模块依赖图:
<script type="module" src="/src/main.js"></script>
CommonJS 与 ESM 的差异不能简化成“一个是值拷贝,一个是引用”:CommonJS 导出的是 module.exports 对象引用,ESM 提供规范化的 live bindings;Node 对 CommonJS 的 ESM 具名导入还依赖静态分析,最可靠的是默认导入。
现代 Node.js:
.mjs明确表示 ESM;.cjs明确表示 CommonJS;package.json的type可以决定.js的默认模式;- CommonJS 可以使用动态
import()加载 ESM; - 不应再把
node --experimental-modules当作普通现代 Node 的必要步骤。
14. Iterator 与 for…of
可迭代对象在 Symbol.iterator 上提供方法,该方法返回包含 next() 的迭代器。next() 返回 { value, done },for…of 会自动消费它,并在提前退出时尝试调用 return()。
原生可迭代对象包括 Array、String、Set、Map、TypedArray、Arguments、NodeList(目标环境支持时)和 Generator 对象。普通对象默认不可迭代:
const iterable = {
*[Symbol.iterator]() {
yield 1
yield 2
}
}
for (const value of iterable) {
console.log(value)
}
for…in遍历可枚举属性键,for…of遍历迭代值;Array.from()、展开语法、Promise.all()、new Set()等都会消费可迭代对象;yield*可以委托给另一个可迭代对象;- 原生数组迭代器会按索引产生值,稀疏数组中的空位通常表现为
undefined。
15. Promise
Promise 有 pending、fulfilled 和 rejected 三种状态。原文使用 resolved 表示成功状态,规范术语更推荐 fulfilled;resolved 在 Promise 解析语义中还可能表示“已经锁定到另一个 Promise/thenable”。
const task = fetch('/api/user')
.then(response => {
if (!response.ok) throw new Error(`HTTP ${response.status}`)
return response.json()
})
.catch(error => {
console.error(error)
throw error
})
.finally(() => {
console.log('清理工作')
})
- Promise 构造器的 executor 会同步执行;
then回调异步进入微任务队列; resolve(thenable)会采用 thenable 的状态,不一定马上 fulfilled;then()返回新的 Promise,回调返回值会成为新 Promise 的结果,抛出的错误会使新 Promise rejected;catch()等价于then(undefined, onRejected);finally()不改变原结果,除非它抛错或返回 rejected Promise;- Promise 不提供原生取消机制,Fetch 可以通过
AbortController取消请求。
组合方法:
Promise.all():全部 fulfilled 才成功,任一 rejected 立即失败,结果按输入顺序排列;Promise.allSettled()(ES2020):等待全部结束,返回{ status, value }或{ status, reason };Promise.race():第一个 settled 的结果决定最终状态;Promise.any()(ES2021):第一个 fulfilled 成功,全部 rejected 时抛出AggregateError;Promise.resolve()、Promise.reject();Promise.withResolvers()(ES2024):返回{ promise, resolve, reject },应谨慎使用以免把状态控制权泄露;Promise.try()(ES2025):把同步调用和异步 Promise 链统一包装。
16. Generator 与 Async
Generator 函数调用后不会立即执行,而是返回迭代器:
function* numbers() {
yield 1
yield 2
return 3
}
const iterator = numbers()
console.log(iterator.next()) // { value: 1, done: false }
console.log(iterator.next()) // { value: 2, done: false }
console.log(iterator.next()) // { value: 3, done: true }
next(value) 会把 value 作为上一次暂停位置中 yield 表达式的结果;第一次 next() 只负责启动。return() 和 throw() 可以结束或向 Generator 注入控制流,try…finally 仍会执行清理逻辑。
Generator 适合实现迭代器和协作式控制流,但现代异步代码通常优先使用 Promise 和 async/await:
async function loadUser() {
const [user, permissions] = await Promise.all([
fetch('/api/user').then(response => response.json()),
fetch('/api/permissions').then(response => response.json())
])
return { user, permissions }
}
async函数始终返回 Promise;return value会使返回 Promise fulfilled,抛错会使其 rejected;await会暂停当前 async 函数,不会阻塞整个 JavaScript 执行线程;- 没有依赖关系的异步操作应使用
Promise.all()并行发起; forEach(async () => {})不会等待回调,顺序执行使用for…of,并发执行使用Promise.all();for await…of可以消费异步可迭代对象和 Promise 序列。
二、ES2016~ES2020

ES2016
- 指数运算符:
2 ** 3; Array.prototype.includes():使用 SameValueZero 判断是否包含值,因此可以找到NaN。
ES2017
async/await;Object.values()、Object.entries()、Object.getOwnPropertyDescriptors();String.prototype.padStart()、padEnd();- 函数参数列表允许尾逗号;
SharedArrayBuffer和Atomics:用于特定的共享内存场景,需要 Worker、跨源隔离和安全策略配合,不能当作普通线程共享变量。
ES2018
- 对象剩余属性和对象展开;
- 正则
s、后行断言、Unicode 属性转义、命名捕获组; Promise.prototype.finally();- 异步迭代器和
for await…of; - 标签模板对非法转义的处理调整。
const source = { a: 1, b: 2 }
const { a, ...rest } = source
const clone = { ...source }
对象展开只复制源对象的可枚举自有属性,不复制原型、不可枚举属性,也只是浅复制。
ES2019
Array.prototype.flat()、flatMap();Object.fromEntries();String.prototype.trimStart()、trimEnd();Array.prototype.sort()的稳定性成为规范要求;Function.prototype.toString()更准确地保留源代码文本(引擎需保存时);catch参数可省略:try { ... } catch { ... };- JSON.stringify 对孤立代理项采用 well-formed JSON 输出。
ES2020
globalThis:跨宿主访问全局对象的标准入口,但不同宿主的全局对象仍可能包含不同 API;BigInt:任意精度整数,字面量使用n后缀;- 可选链
?.; - 空值合并
??; String.prototype.matchAll();- 动态
import(); Promise.allSettled();import.meta;for…in枚举顺序规范化;export * as ns from './module.js';
BigInt 示例:
const huge = 123456789012345678901234567890n
console.log(huge * 2n)
console.log(typeof huge) // bigint
// BigInt 和 Number 不可直接混合运算
// 1n + 1 // TypeError
BigInt.asUintN(width, value) 和 BigInt.asIntN(width, value) 的第一个参数是位宽,不是“固定的 2^53 范围”。BigInt 不能表示小数,不能直接用于 Math 方法,也不能与 Number 混合运算。
可选链和空值合并:
const city = user?.address?.city ?? '未知'
const callbackResult = callback?.(payload)
?? 只把 null 和 undefined 视为空值。它不能与 && 或 || 直接混用,必须加括号:
const value = (a || b) ?? c
三、ES2021~ES2026 的更新
ES2021
- 逻辑赋值:
||=、&&=、??=; - 数字分隔符:
1_000_000; String.prototype.replaceAll();Promise.any()与AggregateError;WeakRef与FinalizationRegistry。
config.timeout ??= 5000
options.debug ||= false
const readable = 1_000_000
ES2022
- 类字段、静态字段、私有字段和私有方法;
- 静态初始化块:
static { ... }; #field in object私有品牌检查;Array.prototype.at();Object.hasOwn();Error的cause;- 正则匹配索引
d标志; - 顶层
await; - 其他 ES2022 能力还包括类静态初始化块等;具体兼容性仍应查看目标环境。
class Registry {
static #items = new Map()
static {
this.#items.set('version', 1)
}
static hasPrivateBrand(item) {
return #items in item
}
}
ES2023
findLast()、findLastIndex();- 不改变原数组的复制式方法:
toReversed()、toSorted()、toSpliced()、with();对应 TypedArray 支持toReversed()、toSorted()和with(),不提供toSpliced(); - Hashbang:脚本首行可以使用
#!/usr/bin/env node; - 非注册 Symbol 作为 WeakMap/WeakSet 键值的能力。
const source = [3, 1, 2]
const sorted = source.toSorted((a, b) => a - b)
console.log(source, sorted) // [3, 1, 2] [1, 2, 3]
ES2024
Object.groupBy()、Map.groupBy();Promise.withResolvers();- Resizable
ArrayBuffer和 GrowableSharedArrayBuffer; Atomics.waitAsync()等并发能力增强。
const groups = Object.groupBy(
[{ type: 'fruit', name: 'apple' }, { type: 'drink', name: 'tea' }],
item => item.type
)
ES2025
Iterator.from()以及同步 Iterator Helpers:map()、filter()、take()、drop()、flatMap()、reduce()、toArray()等;- Set 运算:
union()、intersection()、difference()、symmetricDifference()、isSubsetOf()、isSupersetOf()、isDisjointFrom(); RegExp.escape();Promise.try();- import attributes;
- 重复命名捕获组等正则能力;
- 其他 TypedArray、ArrayBuffer 和错误处理方面的标准更新。
const result = Iterator
.from([1, 2, 3, 4])
.filter(value => value % 2 === 0)
.map(value => value * 10)
.toArray()
const common = new Set([1, 2]).intersection(new Set([2, 3]))
console.log(result, common) // [20, 40] Set { 2 }
const literal = 'a+b'
const pattern = new RegExp(`^${RegExp.escape(literal)}$`)
这些较新的 API 在现代环境中逐步可用,但老浏览器、旧 Node 和部分嵌入式 WebView 仍可能需要转换、polyfill 或能力检测。
ES2026
ECMAScript 2026 继续补充异步收集、错误判断、迭代器组合、二进制编码和数值计算能力:
Array.fromAsync():从异步可迭代对象、可迭代对象或类数组对象异步创建数组;Error.isError():判断一个值是否为 Error 对象,比instanceof Error更适合部分跨 realm 场景;Math.sumPrecise():对可迭代数值进行更精确的浮点求和;Uint8Array的toBase64()、fromBase64()、toHex()、fromHex();Iterator.concat():连接多个可迭代对象;JSON.parse()reviver 的context.source:读取当前原始 JSON 片段;Map/WeakMap的getOrInsert()和getOrInsertComputed();- 其他迭代器、正则和 TypedArray 的细节完善。
const values = await Array.fromAsync([Promise.resolve(1), 2, 3])
const bytes = Uint8Array.fromHex('ff00')
const total = Math.sumPrecise([0.1, 0.2])
const sequence = Iterator.concat([1, 2], new Set([3, 4])).toArray()
这些 API 属于较新的运行时能力,老浏览器、旧 Node 和部分 WebView 仍可能需要能力检测或 polyfill。
四、不要把提案当成标准
原文把以下内容列在“ES 提案”中,并统一标记为已完成,这会造成误解:
do表达式;throw表达式;- 管道运算符
|>; - 绑定运算符
::; - Realm;
- 部分装饰器语法;
- 其他处于 Stage 2/Stage 3 的提案。
提案阶段会变化,Stage 3 也不等于浏览器普遍支持。使用 Babel、TypeScript 或框架编译器提供的装饰器时,应注明它是工具链语法还是 ECMAScript 已发布特性。#! Hashbang、私有字段、Promise.any 等曾经是提案,但现在已经分别进入正式版本,不能继续归入“未完成提案”。
五、模块加载与工程实践
浏览器脚本
<script src="legacy.js" defer></script>
<script type="module" src="main.js"></script>
<script src="analytics.js" async></script>
- 普通经典脚本可能暂停 HTML 解析;
defer经典脚本并行下载、解析完成后按顺序执行;async下载完成后执行,不保证顺序;- 模块脚本默认具有延迟执行语义,依赖图完成后执行;
DOMContentLoaded不会等待普通 async 脚本和图片全部完成,具体事件顺序需结合资源类型判断。
构建与兼容
- 用目标浏览器/Node 版本决定是否转译,而不是默认把所有代码转成 ES5;
- 只为实际目标环境引入 polyfill,避免重复加载和扩大包体积;
- 对动态 import、顶层 await、JSON 模块和装饰器等能力进行构建工具验证;
- 运行时能力判断可以使用
typeof、in、try…catch,不要只根据 User-Agent 猜测; - 语法支持和 API 支持是两件事,Babel 转换语法不一定会提供缺失的运行时 API。
六、总结
- ES2015 引入了块级绑定、解构、模板字符串、箭头函数、类、模块、Promise、Generator、Symbol、集合、Proxy 和 Reflect 等基础能力;
- ES2016~ES2020 持续完善异步、正则、对象、数组、BigInt、可选链和模块;
- ES2021~ES2026 增加了逻辑赋值、replaceAll、Promise.any、类私有字段、顶层 await、不可变数组方法、集合运算、Iterator Helpers、RegExp.escape、Promise.try 和
Array.fromAsync()等; - “ES6”是历史称呼,不能代表今天所有 JavaScript 新特性;
- 标准版本、浏览器支持、Node 支持、Babel/TypeScript 语法和框架扩展必须分开说明;
- 需要精确语义时,应以 ECMAScript 规范和 MDN 兼容性数据为准。