ES 新特性九年速览:从 ES2016 到 ES2024

文章目录

  • 前言
  • 一、ES2016
    • [1.1 Array.prototype.includes](#1.1 Array.prototype.includes)
    • [1.2 指数运算符](#1.2 指数运算符)
  • 二、ES2017
    • [2.1 async / await](#2.1 async / await)
    • [2.2 Object.entries / Object.values](#2.2 Object.entries / Object.values)
    • [2.3 String.prototype.padStart / padEnd](#2.3 String.prototype.padStart / padEnd)
    • [2.4 函数参数尾逗号](#2.4 函数参数尾逗号)
  • 三、ES2018
    • [3.1 Rest / Spread 属性](#3.1 Rest / Spread 属性)
    • [3.2 Promise.prototype.finally](#3.2 Promise.prototype.finally)
    • [3.3 异步迭代](#3.3 异步迭代)
  • 四、ES2019
    • [4.1 Array.prototype.flat / flatMap](#4.1 Array.prototype.flat / flatMap)
    • [4.2 Object.fromEntries](#4.2 Object.fromEntries)
    • [4.3 可选的 catch 绑定](#4.3 可选的 catch 绑定)
  • 五、ES2020
    • [5.1 可选链操作符 ?.](#5.1 可选链操作符 ?.)
    • [5.2 空值合并操作符 ??](#5.2 空值合并操作符 ??)
    • [5.3 BigInt](#5.3 BigInt)
    • [5.4 globalThis](#5.4 globalThis)
    • [5.5 Promise.allSettled](#5.5 Promise.allSettled)
    • [5.6 顶层 await](#5.6 顶层 await)
  • 六、ES2021
    • [6.1 String.prototype.replaceAll](#6.1 String.prototype.replaceAll)
    • [6.2 Promise.any](#6.2 Promise.any)
    • [6.3 逻辑赋值运算符](#6.3 逻辑赋值运算符)
    • [6.4 数值分隔符](#6.4 数值分隔符)
  • 七、ES2022
    • [7.1 Object.hasOwn](#7.1 Object.hasOwn)
    • [7.2 Array.prototype.at](#7.2 Array.prototype.at)
    • [7.3 class 私有属性和方法](#7.3 class 私有属性和方法)
    • [7.4 Error.cause](#7.4 Error.cause)
  • 八、ES2023
    • [8.1 Array 的非破坏性方法](#8.1 Array 的非破坏性方法)
  • 九、ES2024
    • [9.1 Object.groupBy / Map.groupBy](#9.1 Object.groupBy / Map.groupBy)
    • [9.2 Promise.withResolvers](#9.2 Promise.withResolvers)
  • 十、面试聚焦
    • [10.1 ?? vs || 的区别](#10.1 ?? vs || 的区别)
    • [10.2 顶层 await 的条件](#10.2 顶层 await 的条件)
  • 十一、易混淆点
  • 十二、思考与练习
  • 总结

前言

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


一、ES2016

1.1 Array.prototype.includes

javascript 复制代码
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 指数运算符

javascript 复制代码
// 之前
Math.pow(2, 10)  // 1024

// ES2016
2 ** 10          // 1024

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

二、ES2017

2.1 async / await

javascript 复制代码
// 之前: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

javascript 复制代码
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

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

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

2.4 函数参数尾逗号

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

三、ES2018

3.1 Rest / Spread 属性

javascript 复制代码
// 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

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

3.3 异步迭代

javascript 复制代码
// 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

javascript 复制代码
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

javascript 复制代码
// 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 绑定

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

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

五、ES2020

5.1 可选链操作符 ?.

javascript 复制代码
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 空值合并操作符 ??

javascript 复制代码
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

javascript 复制代码
// 超大整数
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

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

5.5 Promise.allSettled

javascript 复制代码
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

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

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

六、ES2021

6.1 String.prototype.replaceAll

javascript 复制代码
const str = 'aabbcc'

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

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

6.2 Promise.any

javascript 复制代码
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 逻辑赋值运算符

javascript 复制代码
// &&=
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 数值分隔符

javascript 复制代码
// 提高大数字的可读性
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

javascript 复制代码
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

javascript 复制代码
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 私有属性和方法

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

7.4 Error.cause

javascript 复制代码
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 的非破坏性方法

javascript 复制代码
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

javascript 复制代码
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

javascript 复制代码
// 之前:需要在外部定义 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 || 的区别

javascript 复制代码
// || 判断所有 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 的条件

javascript 复制代码
// 只能在 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
相关推荐
2401_834636991 小时前
Keepalived + LVS (DR) + Nginx + NFS 高可用 Web 集群部署实战手册
前端·nginx·lvs
和你看星星1 小时前
我把代码排查流程做成了一个 Codex Skill
前端
excel1 小时前
AI 冲击下的前端发展指引:从工具到价值的重塑
前端
文心快码BaiduComate1 小时前
提升组织级AI Coding质量:电商搜索项目实践
前端·后端·程序员
excel1 小时前
AI 时代前端转型:模型训练才是未来的核心竞争力
前端
放下华子我只抽RuiKe52 小时前
FastAPI 全栈后端(四):认证与授权
开发语言·前端·javascript·python·深度学习·react.js·fastapi
持敬chijing2 小时前
Web渗透之前后端漏洞-文件包含漏洞
前端·安全·web安全·网络安全·网络攻击模型·安全威胁分析
CV艺术家2 小时前
前端免费高效的接入天气组件(天气网),控制组件的样式
前端
如果超人不会飞2 小时前
WebMCP:当浏览器学会和 AI「说人话」,你的网页就成了智能体的游乐场
javascript