技术知识文章集合TECHNICAL ARCHIVE · 457 DOCUMENTS

显示模式

登录
ARCHIVE DOCUMENTJS

从 ES2015 到 ES2026:JavaScript 新特性概览

所属馆藏
JavaScript
文件格式
Markdown
原始路径
JavaScript/01-1 5万字概括ES6全部特性(已更新ES2020)
本文目录7 个章节
  1. 一、ES2015(ES6)核心特性
  2. 二、ES2016~ES2020
  3. 三、ES2021~ES2026 的更新
  4. 四、不要把提案当成标准
  5. 五、模块加载与工程实践
  6. 六、总结
  7. 参考资料

从 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 特性思维导图(历史资料)

图中的“ES6”是历史称呼,图中只覆盖到 ES2020,不能作为当前标准的完整清单。

一、ES2015(ES6)核心特性

1. letconst 和块级作用域

let count = 0
const name = 'Ada'

if (true) {
  let count = 1
  const message = `${name}: ${count}`
  console.log(message)
}

console.log(count) // 0
  • var 是函数作用域;在顶层脚本中声明时还会和全局对象产生特定关系,模块顶层则不是简单的 window 属性;
  • letconst 是块级作用域;
  • const 只能保证绑定不能重新赋值,不能冻结对象内部:
const user = { name: 'Ada' }
user.name = 'Grace' // 可以修改对象属性
// user = {}        // TypeError,不能重新绑定
  • letconstclass 和模块导入绑定在声明前处于暂时性死区(TDZ);它们不是“完全没有提升”,而是绑定已经创建但在初始化前不能访问;
  • 同一作用域不能重复声明同名的 letconstclass
  • const 声明时必须初始化;
  • forfor…offor…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]

常见形式:

  • 数组解构:按迭代顺序取值,支持剩余项;
  • 对象解构:按属性名取值,可改名、设置默认值;
  • 参数解构:可以和参数默认值结合;
  • 字符串可以按迭代器解构;
  • SetMap 等可迭代对象可以使用数组模式;
  • 数值和布尔值会被包装后读取属性,因此 const { toString } = 123 可以工作;
  • nullundefined 不能被解构为对象,会抛出 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.EPSILON1 与下一个可表示的 Number 之间的间隔,不是所有计算的“最小精度”;
  • Number.MAX_SAFE_INTEGER2 ** 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):返回匹配组的索引范围;
  • gimsuy 可通过 regex.flags 查询;
  • dotAllunicodestickyhasIndices 等属性反映对应模式;
  • 命名捕获组:/(?<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.iteratorSymbol.toPrimitiveSymbol.toStringTagSymbol.speciesSymbol.hasInstanceSymbol.matchSymbol.replaceSymbol.searchSymbol.splitSymbol.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))

对象键使用引用身份比较;两个内容相同但引用不同的对象是两个不同的键。MapSet 都按插入顺序迭代,提供 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:getsethasdeletePropertydefinePropertyownKeysgetOwnPropertyDescriptorgetPrototypeOfsetPrototypeOfisExtensiblepreventExtensionsapplyconstruct

注意:

  • 必须通过代理引用操作,直接操作目标对象不会触发 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.jsontype 可以决定 .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 有 pendingfulfilledrejected 三种状态。原文使用 resolved 表示成功状态,规范术语更推荐 fulfilledresolved 在 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

ES2015~ES2020 版本关系(历史资料)

ES2016

  • 指数运算符:2 ** 3
  • Array.prototype.includes():使用 SameValueZero 判断是否包含值,因此可以找到 NaN

ES2017

  • async/await
  • Object.values()Object.entries()Object.getOwnPropertyDescriptors()
  • String.prototype.padStart()padEnd()
  • 函数参数列表允许尾逗号;
  • SharedArrayBufferAtomics:用于特定的共享内存场景,需要 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)

?? 只把 nullundefined 视为空值。它不能与 &&|| 直接混用,必须加括号:

const value = (a || b) ?? c

三、ES2021~ES2026 的更新

ES2021

  • 逻辑赋值:||=&&=??=
  • 数字分隔符:1_000_000
  • String.prototype.replaceAll()
  • Promise.any()AggregateError
  • WeakRefFinalizationRegistry
config.timeout ??= 5000
options.debug ||= false
const readable = 1_000_000

ES2022

  • 类字段、静态字段、私有字段和私有方法;
  • 静态初始化块:static { ... }
  • #field in object 私有品牌检查;
  • Array.prototype.at()
  • Object.hasOwn()
  • Errorcause
  • 正则匹配索引 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 和 Growable SharedArrayBuffer
  • 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():对可迭代数值进行更精确的浮点求和;
  • Uint8ArraytoBase64()fromBase64()toHex()fromHex()
  • Iterator.concat():连接多个可迭代对象;
  • JSON.parse() reviver 的 context.source:读取当前原始 JSON 片段;
  • Map/WeakMapgetOrInsert()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 模块和装饰器等能力进行构建工具验证;
  • 运行时能力判断可以使用 typeofintry…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 兼容性数据为准。

参考资料

457 DOCUMENTS · 10 COLLECTIONS
ARCHIVE SEARCH457 篇文章

SEARCH GUIDE

输入关键词开始搜索

支持搜索文章标题、所属分类和原始文档路径。

按分类浏览

10 COLLECTIONS