Skip to content

ES 新特性汇总

ES6 之后,ECMAScript 每年发布一个新版本。本文汇总 ES2016 到 ES2024 的主要新特性。

ES2016 (ES7)#

Array.prototype.includes#

// 之前用 indexOf
;[1, 2, 3].indexOf(2) !== -1 // true
// ES2016
;[1, 2, 3].includes(2) // true
;[1, 2, 3].includes(4) // false
// includes 可以检测 NaN
;[1, NaN, 3].indexOf(NaN) // -1(找不到)
;[1, NaN, 3].includes(NaN) // true
// 从指定位置开始搜索
;[1, 2, 3].includes(1, 1) // false

指数运算符 **#

// 之前
Math.pow(2, 10) // 1024
// ES2016
2 ** 10 // 1024
// 右结合
2 ** (3 ** 2) // 512(等于 2 ** 9)
// 与赋值结合
let num = 2
num **= 10 // 1024

ES2017 (ES8)#

async/await#

async function fetchData() {
const response = await fetch('/api/data')
return response.json()
}

Object.values/Object.entries#

const obj = { a: 1, b: 2, c: 3 }
Object.values(obj) // [1, 2, 3]
Object.entries(obj) // [['a', 1], ['b', 2], ['c', 3]]
// 实用场景
for (const [key, value] of Object.entries(obj)) {
console.log(`${key}: ${value}`)
}

String padding#

'5'.padStart(3, '0') // '005'
'5'.padEnd(3, '0') // '500'
// 格式化
'123'.padStart(8, ' ') // ' 123'

Object.getOwnPropertyDescriptors#

const obj = {
get name() {
return 'foo'
},
}
Object.getOwnPropertyDescriptors(obj)
// {
// name: {
// get: [Function: get name],
// set: undefined,
// enumerable: true,
// configurable: true
// }
// }
// 用于完整复制对象
const clone = Object.create(
Object.getPrototypeOf(obj),
Object.getOwnPropertyDescriptors(obj)
)

函数参数尾逗号#

function foo(
param1,
param2 // 允许尾逗号
) {}
foo(
'a',
'b' // 允许尾逗号
)

SharedArrayBuffer 和 Atomics#

// 共享内存
const sab = new SharedArrayBuffer(1024)
const view = new Int32Array(sab)
// 原子操作
Atomics.add(view, 0, 5)
Atomics.load(view, 0)

ES2018 (ES9)#

异步迭代#

async function* asyncGenerator() {
yield await fetch('/api/1')
yield await fetch('/api/2')
}
for await (const response of asyncGenerator()) {
console.log(response)
}

Promise.finally#

fetchData()
.then((data) => console.log(data))
.catch((err) => console.error(err))
.finally(() => console.log('完成'))

Rest/Spread 属性#

// Rest 属性
const { a, ...rest } = { a: 1, b: 2, c: 3 }
// a = 1, rest = { b: 2, c: 3 }
// Spread 属性
const merged = { ...obj1, ...obj2 }

正则表达式增强#

// 命名捕获组
const re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/
const match = re.exec('2024-01-15')
match.groups.year // '2024'
match.groups.month // '01'
match.groups.day // '15'
// 后行断言
;/(?<=\$)\d+/.exec('$100') // ['100']
;/(?<!\$)\d+/.exec('€100') // ['100']
// dotAll 模式
;/foo.bar/s.test('foo\nbar') // true
// Unicode 属性转义
;/\p{Script=Han}/u.test('') // true

ES2019 (ES10)#

Array.prototype.flat/flatMap#

;[1, [2, [3]]].flat() // [1, 2, [3]]
;[1, [2, [3]]].flat(2) // [1, 2, 3]
;[1, [2, [3]]].flat(Infinity) // [1, 2, 3]
;[1, 2].flatMap((x) => [x, x * 2]) // [1, 2, 2, 4]

Object.fromEntries#

const entries = [
['a', 1],
['b', 2],
]
Object.fromEntries(entries) // { a: 1, b: 2 }
// Map 转对象
const map = new Map([
['a', 1],
['b', 2],
])
Object.fromEntries(map) // { a: 1, b: 2 }
// URLSearchParams 转对象
const params = new URLSearchParams('a=1&b=2')
Object.fromEntries(params) // { a: '1', b: '2' }

String.prototype.trimStart/trimEnd#

' hello '.trimStart() // 'hello '
' hello '.trimEnd() // ' hello'

可选的 catch 绑定#

// 之前
try {
// ...
} catch (error) {
// 必须声明 error
}
// ES2019
try {
JSON.parse(input)
} catch {
// 不需要 error 参数
console.log('解析失败')
}

Symbol.prototype.description#

const sym = Symbol('描述')
sym.description // '描述'

Function.prototype.toString 修订#

function /* 注释 */ foo() {}
foo.toString() // 'function /* 注释 */ foo() {}'
// 现在保留注释和空白

ES2020 (ES11)#

可选链 ?.#

const user = { profile: { name: '张三' } }
user?.profile?.name // '张三'
user?.address?.city // undefined(不报错)
// 方法调用
obj.method?.()
// 数组访问
arr?.[0]

空值合并 ??#

const value = null ?? 'default' // 'default'
const value2 = 0 ?? 'default' // 0
// 与 || 的区别
0 || 'default' // 'default'
0 ?? 'default' // 0
'' || 'default' // 'default'
'' ?? 'default' // ''

BigInt#

const big = 9007199254740993n
const big2 = BigInt('9007199254740993')
big + 1n // 9007199254740994n
// 不能与普通数字混合运算
// big + 1; // TypeError

Promise.allSettled#

const results = await Promise.allSettled([
Promise.resolve(1),
Promise.reject(new Error('失败')),
Promise.resolve(3),
])
// [
// { status: 'fulfilled', value: 1 },
// { status: 'rejected', reason: Error },
// { status: 'fulfilled', value: 3 }
// ]

globalThis#

// 统一的全局对象访问
globalThis.setTimeout // 浏览器和 Node.js 都可用

String.prototype.matchAll#

const str = 'test1test2test3'
const matches = [...str.matchAll(/test(\d)/g)]
// [['test1', '1'], ['test2', '2'], ['test3', '3']]

动态 import()#

const module = await import('./module.js')

export * as#

export * as utils from './utils.js'

ES2021 (ES12)#

String.prototype.replaceAll#

'aabbcc'.replaceAll('b', 'x') // 'aaxxcc'
// 之前需要正则
'aabbcc'.replace(/b/g, 'x') // 'aaxxcc'

Promise.any#

const first = await Promise.any([
fetch('/api/1'),
fetch('/api/2'),
fetch('/api/3'),
])
// 返回第一个成功的结果

逻辑赋值运算符#

// ||=
a ||= b // a = a || b
// &&=
a &&= b // a = a && b
// ??=
a ??= b // a = a ?? b
// 实用场景
user.name ??= 'Anonymous'

WeakRef 和 FinalizationRegistry#

// 弱引用
const ref = new WeakRef(targetObject)
const obj = ref.deref() // 可能是 undefined
// 清理回调
const registry = new FinalizationRegistry((heldValue) => {
console.log('对象被回收:', heldValue)
})
registry.register(obj, 'some value')

数字分隔符#

const billion = 1_000_000_000
const bytes = 0xff_ff_ff_ff
const bits = 0b1010_0001

ES2022 (ES13)#

类字段和私有成员#

class Person {
name = 'default' // 公有字段
#age = 0 // 私有字段
static count = 0 // 静态字段
static #secret = 'x' // 私有静态字段
#privateMethod() {} // 私有方法
static #staticPrivate() {} // 私有静态方法
}

静态初始化块#

class Config {
static data
static {
// 复杂的静态初始化
this.data = loadConfig()
}
}

顶层 await#

// 模块顶层可以使用 await
const data = await fetch('/api/config').then((r) => r.json())
export { data }

in 操作符检查私有字段#

class Foo {
#brand
static isFoo(obj) {
return #brand in obj
}
}

Array.prototype.at#

const arr = [1, 2, 3, 4, 5]
arr.at(-1) // 5
arr.at(-2) // 4
arr.at(0) // 1
'hello'.at(-1) // 'o'

Object.hasOwn#

const obj = { prop: 'value' }
Object.hasOwn(obj, 'prop') // true
// 替代 hasOwnProperty
obj.hasOwnProperty('prop') // true
// hasOwn 更安全,不受原型链影响

RegExp match indices#

const re = /a+(?<Z>z)?/d
const match = re.exec('aaaz')
match.indices // [[0, 4], [3, 4]]
match.indices.groups // { Z: [3, 4] }

Error cause#

try {
doSomething()
} catch (error) {
throw new Error('操作失败', { cause: error })
}

ES2023 (ES14)#

数组查找从后往前#

const arr = [1, 2, 3, 2, 1]
arr.findLast((x) => x > 1) // 2
arr.findLastIndex((x) => x > 1) // 3

数组不可变方法#

const arr = [3, 1, 2]
arr.toSorted() // [1, 2, 3],原数组不变
arr.toReversed() // [2, 1, 3],原数组不变
arr.toSpliced(1, 1, 'a') // [3, 'a', 2],原数组不变
arr.with(1, 'x') // [3, 'x', 2],原数组不变
// 原数组保持不变
console.log(arr) // [3, 1, 2]

Hashbang 语法#

#!/usr/bin/env node
console.log('Hello')

Symbol.prototype 作为 WeakMap 键#

const weak = new WeakMap()
const key = Symbol('key')
weak.set(key, 'value')

ES2024 (ES15)#

Promise.withResolvers#

const { promise, resolve, reject } = Promise.withResolvers()
// 等价于
let resolve, reject
const promise = new Promise((res, rej) => {
resolve = res
reject = rej
})

Object.groupBy / Map.groupBy#

const items = [
{ type: 'fruit', name: 'apple' },
{ type: 'fruit', name: 'banana' },
{ type: 'vegetable', name: 'carrot' },
]
Object.groupBy(items, (item) => item.type)
// {
// fruit: [{ type: 'fruit', name: 'apple' }, ...],
// vegetable: [{ type: 'vegetable', name: 'carrot' }]
// }
Map.groupBy(items, (item) => item.type)
// Map { 'fruit' => [...], 'vegetable' => [...] }

正则表达式 v 标志#

// 集合操作
;/[\p{Script=Greek}&&\p{Letter}]/v // 交集
;/[\p{Script=Greek}--[αβγ]]/v // 差集
// 字符串字面量
;/[\q{abc|def}]/v.test('abc') // true

ArrayBuffer 可调整大小#

const buffer = new ArrayBuffer(8, { maxByteLength: 16 })
buffer.resize(12)
buffer.byteLength // 12

Atomics.waitAsync#

const sab = new SharedArrayBuffer(4)
const view = new Int32Array(sab)
// 异步等待
Atomics.waitAsync(view, 0, 0).value.then(() => {
console.log('值已改变')
})

特性汇总表#

| 版本 | 年份 | 主要特性 | | ------ | ---- | ------------------------------- | --- | --- | | ES2016 | 2016 | includes, ** | | ES2017 | 2017 | async/await, Object.entries | | ES2018 | 2018 | 异步迭代, Rest/Spread, 正则增强 | | ES2019 | 2019 | flat/flatMap, fromEntries | | ES2020 | 2020 | ?., ??, BigInt, allSettled | | ES2021 | 2021 | replaceAll, Promise.any, | | = | | ES2022 | 2022 | 类私有成员, 顶层 await, at() | | ES2023 | 2023 | findLast, 不可变数组方法 | | ES2024 | 2024 | withResolvers, groupBy, v 标志 |

持续关注 ECMAScript 新特性,能让代码更简洁、更现代。