前言

ES2016(ES7)到 ES2024(ES15)引入了大量实用特性,掌握这些新特性能让代码更简洁、更安全。本篇速览各版本的重要更新。


一、ES2016

1.1 Array.prototype.includes

const arr = [1, 2, 3, NaN]

// indexOf 无法正确判断 NaN
arr.indexOf(NaN)    // -1

// includes 可以正确判断 NaN
arr.includes(NaN)   // true
arr.includes(2)     // true
arr.includes(4)     // false

1.2 指数运算符

// 之前
Math.pow(2, 10)  // 1024

// ES2016
2 ** 10          // 1024

// 结合赋值
let x = 2
x **= 3          // x = x ** 3 = 8

二、ES2017

2.1 async / await

// 之前:Promise 链
function fetchData() {
  return fetch('/api/user')
    .then(res => res.json())
    .then(user => fetch(`/api/posts/${user.id}`))
    .then(res => res.json())
}

// ES2017:async/await
async function fetchData() {
  const res = await fetch('/api/user')
  const user = await res.json()
  const postsRes = await fetch(`/api/posts/${user.id}`)
  return postsRes.json()
}

// 错误处理
async function safeFetch(url) {
  try {
    const res = await fetch(url)
    return await res.json()
  } catch (err) {
    console.error('请求失败:', err.message)
    return null
  }
}

2.2 Object.entries / Object.values

const obj = { a: 1, b: 2, c: 3 }

// Object.entries:返回键值对数组
Object.entries(obj)  // [['a', 1], ['b', 2], ['c', 3]]

// Object.values:返回值数组
Object.values(obj)   // [1, 2, 3]

// 配合 for...of 遍历
for (const [key, value] of Object.entries(obj)) {
  console.log(key, value)
}

2.3 String.prototype.padStart / padEnd

// 补齐字符串长度
'5'.padStart(2, '0')    // '05'
'hello'.padEnd(10, '!') // 'hello!!!!!'

// 常用于格式化
const time = '9'
`${time.padStart(2, '0')}:00`  // '09:00'

2.4 函数参数尾逗号

// 允许函数参数列表末尾有逗号
function add(
  a,
  b,  // 尾逗号,方便添加新参数
) {
  return a + b
}

三、ES2018

3.1 Rest / Spread 属性

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

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

3.2 Promise.prototype.finally

fetch('/api/data')
  .then(res => res.json())
  .then(data => console.log(data))
  .catch(err => console.error(err))
  .finally(() => {
    // 无论成功或失败都会执行
    hideLoading()
  })

3.3 异步迭代

// for await...of
async function* asyncGenerator() {
  yield await Promise.resolve(1)
  yield await Promise.resolve(2)
  yield await Promise.resolve(3)
}

for await (const value of asyncGenerator()) {
  console.log(value)  // 1, 2, 3
}

四、ES2019

4.1 Array.prototype.flat / flatMap

const arr = [1, [2, 3], [4, [5, 6]]]

// flat:扁平化数组
arr.flat()       // [1, 2, 3, 4, [5, 6]]
arr.flat(2)      // [1, 2, 3, 4, 5, 6]
arr.flat(Infinity) // 完全扁平化

// flatMap:先 map 再 flat
const arr2 = [1, 2, 3]
arr2.flatMap(x => [x, x * 2])  // [1, 2, 2, 4, 3, 6]

4.2 Object.fromEntries

// Object.entries 的逆操作
const entries = [['a', 1], ['b', 2]]
Object.fromEntries(entries)  // { a: 1, b: 2 }

// 常用于转换 URL 参数
const url = new URLSearchParams('foo=1&bar=2')
Object.fromEntries(url)  // { foo: '1', bar: '2' }

4.3 可选的 catch 绑定

// 之前:必须有 catch 参数
try {
  JSON.parse('invalid')
} catch (e) {
  // 必须写 e,即使不使用
}

// ES2019:可以省略 catch 参数
try {
  JSON.parse('invalid')
} catch {
  // 不需要写参数
}

五、ES2020

5.1 可选链操作符 ?.

const user = {
  address: {
    street: 'Main St'
  }
}

// 之前:需要层层判断
const street = user && user.address && user.address.street

// ES2020:可选链
const street = user?.address?.street

// 调用方法
user?.getName?.()

// 访问数组元素
arr?.[0]

5.2 空值合并操作符 ??

const value = null ?? 'default'  // 'default'
const value2 = 0 ?? 'default'    // 0
const value3 = '' ?? 'default'   // ''

// 与 || 的区别
const value4 = 0 || 'default'    // 'default'(0 是 falsy)
const value5 = 0 ?? 'default'    // 0(只判断 null/undefined)

// ?? 只判断 null 和 undefined
// || 判断所有 falsy 值(0, '', false, null, undefined, NaN)

5.3 BigInt

// 超大整数
const max = Number.MAX_SAFE_INTEGER  // 9007199254740991
const big = 9007199254740991n        // BigInt
const big2 = BigInt(9007199254740991)

// 运算
const sum = big + 1n  // 9007199254740992n

// 注意:BigInt 和 Number 不能混合运算
// const result = big + 1  // TypeError

5.4 globalThis

// 获取全局对象的统一方式
// 浏览器是 window,Node.js 是 global,Web Worker 是 self
globalThis  // 统一的全局对象

5.5 Promise.allSettled

const promises = [
  Promise.resolve(1),
  Promise.reject('error'),
  Promise.resolve(3)
]

// Promise.all:任一 reject 就立即 reject
Promise.all(promises)  // reject 'error'

// Promise.allSettled:等待所有完成
Promise.allSettled(promises)
// [
//   { status: 'fulfilled', value: 1 },
//   { status: 'rejected', reason: 'error' },
//   { status: 'fulfilled', value: 3 }
// ]

5.6 顶层 await

// 在模块顶层可以使用 await
const response = await fetch('/api/data')
const data = await response.json()

// 限制:只能在 ES 模块中使用(type="module" 或 .mjs)

六、ES2021

6.1 String.prototype.replaceAll

const str = 'aabbcc'

// 之前:正则
str.replace(/a/g, 'x')  // 'xxbbcc'

// ES2021
str.replaceAll('a', 'x')  // 'xxbbcc'

6.2 Promise.any

const promises = [
  Promise.reject('error1'),
  Promise.reject('error2'),
  Promise.resolve(3)
]

// 任一 resolve 就 resolve
Promise.any(promises)  // resolve 3

// 全部 reject 才 reject
// AggregateError: All promises were rejected

6.3 逻辑赋值运算符

// &&=
a &&= b  // 等价于 a && (a = b)

// ||=
a ||= b  // 等价于 a || (a = b)

// ??=
a ??= b  // 等价于 a ?? (a = b)

// 示例
let config = {}
config.timeout ??= 3000  // 如果 timeout 是 null/undefined,则设置为 3000

6.4 数值分隔符

// 提高大数字的可读性
const billion = 1_000_000_000
const bytes = 0xFF_FF_FF_FF
const fraction = 0.123_456_789

// 分隔符不影响数值
1_000 === 1000  // true

七、ES2022

7.1 Object.hasOwn

const obj = { a: 1 }

// 之前:hasOwnProperty
obj.hasOwnProperty('a')  // true

// ES2022:Object.hasOwn(推荐)
Object.hasOwn(obj, 'a')  // true

// 优势:即使对象没有继承 Object.prototype 也能用
const obj2 = Object.create(null)
obj2.a = 1
obj2.hasOwnProperty('a')  // TypeError
Object.hasOwn(obj2, 'a')  // true

7.2 Array.prototype.at

const arr = [1, 2, 3, 4, 5]

// 之前:获取最后一个元素
arr[arr.length - 1]  // 5
arr.slice(-1)[0]     // 5

// ES2022:at()
arr.at(-1)  // 5
arr.at(0)   // 1
arr.at(-2)  // 4

7.3 class 私有属性和方法

class Counter {
  // 私有属性
  #count = 0
  
  // 静态私有属性
  static #total = 0
  
  // 私有方法
  #increment() {
    this.#count++
  }
  
  increment() {
    this.#increment()
    Counter.#total++
  }
  
  get count() {
    return this.#count
  }
}

7.4 Error.cause

try {
  try {
    JSON.parse('invalid')
  } catch (err) {
    throw new Error('Parse failed', { cause: err })
  }
} catch (err) {
  console.log(err.message)        // 'Parse failed'
  console.log(err.cause.message)  // 'Unexpected token...'
}

八、ES2023

8.1 Array 的非破坏性方法

const arr = [3, 1, 2]

// toSorted:非破坏性排序
arr.toSorted()  // [1, 2, 3],原数组不变
arr             // [3, 1, 2]

// toReversed:非破坏性反转
arr.toReversed() // [2, 1, 3],原数组不变

// toSpliced:非破坏性 splice
arr.toSpliced(1, 1) // [3, 2],原数组不变

// with:非破坏性修改指定位置
arr.with(0, 10)     // [10, 1, 2],原数组不变

九、ES2024

9.1 Object.groupBy / Map.groupBy

const people = [
  { name: 'Alice', age: 25 },
  { name: 'Bob', age: 30 },
  { name: 'Charlie', age: 25 }
]

// 按年龄分组
const grouped = Object.groupBy(people, p => p.age)
// {
//   25: [{ name: 'Alice', age: 25 }, { name: 'Charlie', age: 25 }],
//   30: [{ name: 'Bob', age: 30 }]
// }

9.2 Promise.withResolvers

// 之前:需要在外部定义 resolve/reject
let resolve, reject
const promise = new Promise((res, rej) => {
  resolve = res
  reject = rej
})

// ES2024
const { promise, resolve, reject } = Promise.withResolvers()

十、面试聚焦

10.1 ?? vs || 的区别

// || 判断所有 falsy 值
0 || 'default'        // 'default'
'' || 'default'       // 'default'
false || 'default'    // 'default'
null || 'default'     // 'default'

// ?? 只判断 null 和 undefined
0 ?? 'default'        // 0
'' ?? 'default'       // ''
false ?? 'default'    // false
null ?? 'default'     // 'default'

10.2 顶层 await 的条件

// 只能在 ES 模块中使用顶层 await
// 1. <script type="module">
// 2. .mjs 文件
// 3. package.json 中 "type": "module"

// CommonJS 模块中不能使用
// 顶层 await 会阻塞当前模块的加载

十一、易混淆点

  1. ?? vs ||?? 只判断 null/undefined|| 判断所有 falsy 值(0, ‘’, false 等)。
  2. ?. 的短路特性a?.b.c 中如果 a 是 null/undefined,b.c 不会执行。
  3. 顶层 await:只能在 ES 模块中使用,会阻塞当前模块的加载。
  4. Object.hasOwn vs hasOwnPropertyObject.hasOwn 更安全,即使对象没有继承 Object.prototype 也能用。
  5. ES2023 非破坏性方法toSortedtoReversed 等返回新数组,不修改原数组。

十二、思考与练习

1. 0 ?? 'default'0 || 'default' 的结果分别是什么?为什么?

解析:

  • 0 ?? 'default'0(?? 只判断 null/undefined)
  • 0 || 'default''default'(|| 判断所有 falsy 值,0 是 falsy)

2. 什么是顶层 await?有什么限制?

解析:

  • 顶层 await 允许在模块顶层使用 await,无需包裹在 async 函数中
  • 限制:只能在 ES 模块中使用(<script type="module">.mjs

3. Object.hasOwnhasOwnProperty 好在哪里?

解析:

  • hasOwnProperty 依赖原型链,如果对象没有继承 Object.prototype 会报错
  • Object.hasOwn 是静态方法,不依赖原型链,更安全

4. Array.prototype.at(-1) 的作用是什么?

解析:获取数组的最后一个元素,等价于 arr[arr.length - 1],但更简洁,支持负索引。

5. ES2023 的 toSortedsort 有什么区别?

解析:

  • sort() 是破坏性的,会修改原数组
  • toSorted() 是非破坏性的,返回新数组,原数组不变

总结

  • ES2016includes** 指数运算符
  • ES2017async/awaitObject.entries/valuespadStart/padEnd
  • ES2018:Rest/Spread 属性、Promise.finally、异步迭代
  • ES2019flat/flatMapObject.fromEntries、可选 catch 绑定
  • ES2020?. 可选链、?? 空值合并、BigIntglobalThisPromise.allSettled、顶层 await
  • ES2021replaceAllPromise.any、逻辑赋值运算符、数值分隔符
  • ES2022Object.hasOwnat()、class 私有属性/方法、Error.cause
  • ES2023toSorted/toReversed/toSpliced/with 非破坏性方法
  • ES2024Object.groupByPromise.withResolvers
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐