技术干货 # nodejs # 前端已发布

ES2016 到 ES2026:JavaScript 十年间那些真正值得使用的新特性

JavaScript 发展到今天,已经很难再用“脚本语言”三个字概括。从 ES2016 开始,ECMAScript 正式进入了每年发布一个版本的节奏。过去十年里,JavaScript 增加了大量新语法、新 API 和新的异步编程能力。很多今天看起来理所当然的写法,其实都是近几年才加入标准的。

2026.07.1911 分钟阅读
ES2016 到 ES2026:JavaScript 十年间那些真正值得使用的新特性
ES2016 到 ES2026:JavaScript 十年间那些真正值得使用的新特性 · Cover

JavaScript 发展到今天,已经很难再用“脚本语言”三个字概括。

从 ES2016 开始,ECMAScript 正式进入了每年发布一个版本的节奏。过去十年里,JavaScript 增加了大量新语法、新 API 和新的异步编程能力。很多今天看起来理所当然的写法,其实都是近几年才加入标准的。

例如:

user?.profile?.name
const name = user?.name ?? 'Unknown'
const result = users
  .filter(user => user.enabled)
  .map(user => user.name)
const data = await fetch(url).then(res => res.json())

这些现代 JavaScript 写法,如果放到十年前,很多都无法直接使用。

本文从 ES2016 一直梳理到 ES2026,不追求把每一个标准变更都罗列出来,而是重点介绍那些实际开发中值得掌握、值得迁移、值得写进项目里的特性。

ES2016 是新的年度 ECMAScript 发布机制下的第一版;之后 ECMAScript 基本按照每年一个版本持续演进。


一、先搞清楚 ES、ECMAScript 和 JavaScript

经常有人问:

ES6、ES2016、ES2020 和 JavaScript 到底是什么关系?

可以简单理解:

ECMAScript
    ↓
JavaScript 遵循的语言标准
    ↓
浏览器 / Node.js / Deno / Bun 等运行时实现

而:

ES6

其实就是:

ECMAScript 2015

从 ES2016 开始,版本命名逐渐采用年份:

ES2016
ES2017
ES2018
ES2019
...
ES2026

所以现在再说:

ES6

更多是在说 JavaScript 的一次巨大语言升级,而不是最新标准。


二、ES2016:两个小功能,却非常实用

ES2016 是第一个按照年度节奏发布的 ECMAScript 版本,规模并不大,最值得记住的是两个功能:

Array.prototype.includes()
指数运算符 **

includes()

过去判断数组是否包含某个元素,经常使用:

[1, 2, 3].indexOf(2) !== -1

ES2016 后:

[1, 2, 3].includes(2)

结果:

true

代码明显更符合语义。

特别是判断字符串数组:

const roles = ['admin', 'editor', 'user']

if (roles.includes('admin')) {
  // ...
}

相比:

roles.indexOf('admin') !== -1

includes() 更容易阅读。


指数运算符

以前:

Math.pow(2, 10)

现在:

2 ** 10

结果:

1024

虽然这个功能不如后面的 async/await、可选链重要,但它标志着 JavaScript 开始逐渐补齐现代语言常见的语法能力。


三、ES2017:async/await 彻底改变异步 JavaScript

如果要选一个对现代 JavaScript 影响最大的特性之一,那么:

async/await

绝对排得上号。

ES2017 正式加入 Async Functions。

在 async/await 出现之前,异步代码经常写成:

getUser(id)
  .then(user => {
    return getOrders(user.id)
  })
  .then(orders => {
    return getProducts(orders)
  })
  .then(products => {
    console.log(products)
  })
  .catch(error => {
    console.error(error)
  })

现在:

async function getUserData(id) {
  try {
    const user = await getUser(id)
    const orders = await getOrders(user.id)
    const products = await getProducts(orders)

    return products
  } catch (error) {
    console.error(error)
  }
}

代码从:Promise 链变成了:看起来像同步代码实际上仍然是异步代码这对 Node.js 后端开发尤其重要。

如果你使用过 NestJS,那么现代 NestJS 代码中几乎到处都是:

async createUser() {
  const user = await this.userRepository.save(data)

  return user
}

Object.values() 和 Object.entries()

ES2017 还增加了:

Object.values()
Object.entries()

以前:

const user = {
  name: 'Tom',
  age: 18
}

Object.keys(user)

现在可以直接:

Object.values(user)

得到:

['Tom', 18]

以及:

Object.entries(user)

得到:

[
  ['name', 'Tom'],
  ['age', 18]
]

这和后来的:

Object.fromEntries()

组合起来非常好用。


四、ES2018:对象展开、异步迭代和更强的正则

ES2018 开始,JavaScript 的现代代码风格开始越来越明显。

最常用的两个:

对象展开
异步迭代

对象 Spread

以前复制对象经常:

Object.assign({}, user)

现在:

const newUser = {
  ...user
}

修改某一个属性:

const newUser = {
  ...user,
  age: 20
}

这已经成为现代 JavaScript 中最常见的对象操作方式之一。


数组和对象的 Rest

例如:

const user = {
  id: 1,
  name: 'Tom',
  age: 18
}

const {
  id,
  ...profile
} = user

得到:

id === 1

profile === {
  name: 'Tom',
  age: 18
}

这在接口参数处理中特别常见:

const {
  password,
  ...userInfo
} = user

可以方便地把敏感字段排除出去。


for await...of

ES2018 支持异步迭代:

for await (const item of asyncIterable) {
  console.log(item)
}

这个能力在:

流式数据
异步数据源
Node.js Stream
分页数据
网络数据

等场景非常有用。

ES2018 同时引入了 async generator:

async function* getUsers() {
  yield await getUser(1)
  yield await getUser(2)
  yield await getUser(3)
}

然后:

for await (const user of getUsers()) {
  console.log(user)
}

五、ES2019:数组处理开始变得舒服

ES2019 有几个非常实用的 API:

Array.flat()
Array.flatMap()
Object.fromEntries()

flat()

例如:

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

arr.flat()

得到:

[1, 2, 3, 4, 5, 6]

多层数组:

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

arr.flat(Infinity)

可以直接展开所有层级。


flatMap()

例如:

const users = [
  {
    name: 'Tom',
    roles: ['admin', 'user']
  },
  {
    name: 'Jack',
    roles: ['user']
  }
]

可以:

const roles = users.flatMap(user => user.roles)

得到:

[
  'admin',
  'user',
  'user'
]

它相当于:

map()
+
flat()

Object.fromEntries()

如果:

Object.entries(user)

是:

Object
 ↓
Array

那么:

Object.fromEntries()

就是:

Array
 ↓
Object

例如:

const entries = [
  ['name', 'Tom'],
  ['age', 18]
]

const user = Object.fromEntries(entries)

得到:

{
  name: 'Tom',
  age: 18
}

这在对象转换、过滤属性时特别好用:

const user = {
  name: 'Tom',
  age: 18,
  password: '123456'
}

const safeUser = Object.fromEntries(
  Object.entries(user)
    .filter(([key]) => key !== 'password')
)

六、ES2020:现代 JavaScript 真正的分水岭

如果说 ES2017 让异步代码发生了变化,那么 ES2020 则让现代 JavaScript 的日常代码风格发生了非常明显的变化。

这一版最重要的几个特性:

?. 可选链
?? 空值合并
Promise.allSettled()
BigInt
globalThis
dynamic import()

可选链 ?.

以前:

const name =
  user &&
  user.profile &&
  user.profile.name

现在:

const name = user?.profile?.name

如果:

user === null

整个表达式不会继续抛出:

Cannot read properties of null

而是得到:

undefined

可选方法调用

不仅可以访问属性:

user?.profile?.name

还可以调用方法:

user?.sayHello?.()

如果方法不存在,不会直接报错。


空值合并 ??

这个和 || 非常容易混淆。

例如:

const count = 0

const result = count || 10

结果:

10

因为 0 被认为是假值。

但是:

const result = count ?? 10

结果:

0

?? 只有在:

null
undefined

时才使用默认值。

因此:

const page = options.page ?? 1

通常比:

const page = options.page || 1

更加准确。


可选链 + 空值合并

现代 JavaScript 最经典的组合之一:

const name = user?.profile?.name ?? 'Unknown'

意思就是:

如果 user、profile 或 name 不存在,就使用 Unknown。

这已经成为现代 Node.js / Vue / React / TypeScript 项目中的标配写法。


七、Promise.allSettled()

以前:

await Promise.all(tasks)

只要其中一个 Promise 失败:

Promise.all
    ↓
一个失败
    ↓
整个 Promise reject

但有时候我们需要:

不管每个任务成功还是失败,都把结果收集回来。

这时候:

const results = await Promise.allSettled(tasks)

例如:

const results = await Promise.allSettled([
  sendEmail(),
  sendSms(),
  sendWebhook()
])

结果类似:

[
  {
    status: 'fulfilled',
    value: ...
  },
  {
    status: 'rejected',
    reason: ...
  },
  {
    status: 'fulfilled',
    value: ...
  }
]

特别适合:

批量请求
批量通知
批量任务
批量 API 调用

八、BigInt

JavaScript 传统的 Number 对整数精度存在限制。

例如:

Number.MAX_SAFE_INTEGER

是:

9007199254740991

超过这个范围以后,整数精度可能出现问题。

ES2020 引入:

BigInt

例如:

const id = 9007199254740992n

注意末尾:

n

运算:

const a = 100n
const b = 200n

console.log(a + b)

结果:

300n

但是:

100n + 1

是不允许的。

必须保证两边类型一致:

100n + 1n

在处理:

超大整数
数据库 BIGINT
高精度 ID
金融计算中的整数场景

时非常有用。


九、dynamic import()

ES2020 支持:

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

这和传统:

import module from './module.js'

不同。

传统 import 通常在模块加载阶段就确定。

动态 import 可以根据运行时条件加载:

if (needEditor) {
  const editor = await import('./editor.js')
}

这对:

代码分割
懒加载
插件系统
按需加载

非常重要。


十、ES2021:让对象和 Promise 操作进一步简洁

ES2021 最值得记住:

String.replaceAll()
Promise.any()
逻辑赋值
数字分隔符

replaceAll()

以前:

str.replace(/foo/g, 'bar')

现在:

str.replaceAll('foo', 'bar')

例如:

const text = 'foo foo foo'

text.replaceAll('foo', 'bar')

结果:

bar bar bar

语义非常直接。


Promise.any()

const result = await Promise.any([
  requestFromServerA(),
  requestFromServerB(),
  requestFromServerC()
])

只要其中一个成功:

返回第一个成功结果

如果全部失败:

AggregateError

这特别适合:

多节点请求
多 CDN
多数据源
容灾请求

十一、逻辑赋值运算符

ES2021 增加:

&&=
||=
??=

例如:

let name = null

name ??= 'Tom'

等价于:

if (name == null) {
  name = 'Tom'
}

再比如:

user.name ||= 'Unknown'

如果 user.name 是假值,就赋值。

而:

user.name ??= 'Unknown'

只在:

null
undefined

时赋值。

这个特性在配置对象处理中非常方便。


十二、ES2022:现代 JavaScript 类开始真正成熟

ES2022 有几个很值得掌握的能力:

类字段
私有字段 #
static block
top-level await
Error cause
Array.at()
Object.hasOwn()

类字段

以前:

class User {
  constructor() {
    this.name = 'Tom'
  }
}

现在:

class User {
  name = 'Tom'
  age = 18
}

更清晰。


私有字段

ES2022 正式支持真正的私有字段:

class User {
  #password

  constructor(password) {
    this.#password = password
  }

  checkPassword(password) {
    return this.#password === password
  }
}

外部无法直接:

user.#password

这是语法层面的私有字段,而不是简单的命名约定。


static block

类中可以定义:

class Config {
  static value

  static {
    Config.value = loadConfig()
  }
}

适合:

复杂静态初始化
类级别初始化逻辑

十三、Top-level await

以前 await 通常只能出现在:

async function

内部。

ES2022 允许 ES Module 顶层直接使用:

const config = await loadConfig()

console.log(config)

例如:

const response = await fetch(
  'https://example.com/config.json'
)

const config = await response.json()

这对于 Node.js ESM、现代前端构建工具和模块初始化非常方便。


十四、Error Cause

现代系统排查错误时,经常需要知道:

当前错误到底是由哪个底层错误引起的?

ES2022 支持:

try {
  await database.query()
} catch (error) {
  throw new Error(
    'Failed to load user',
    {
      cause: error
    }
  )
}

这样就可以保留错误链:

业务异常
   ↓
数据库异常
   ↓
网络异常

这对于后端服务尤其有价值。


十五、Array.at()

以前获取最后一个元素:

arr[arr.length - 1]

现在:

arr.at(-1)

例如:

const numbers = [1, 2, 3]

numbers.at(-1)

得到:

3

同样支持:

numbers.at(-2)

得到:

2

这个 API 很小,但非常舒服。


十六、Object.hasOwn()

以前判断对象自身属性:

Object.prototype.hasOwnProperty.call(
  user,
  'name'
)

现在:

Object.hasOwn(user, 'name')

简单很多。

这也是 ES2022 中一个很值得记住的实用 API。


十七、ES2023:数组终于有了一套“不修改原数组”的 API

这是 ES2023 非常值得关注的一组变化。

新增:

toSorted()
toReversed()
toSpliced()
with()
findLast()
findLastIndex()

其中最重要的思想是:

返回新数组,而不是修改原数组。


toSorted()

传统:

const arr = [3, 1, 2]

arr.sort()

sort() 会修改原数组。

现在:

const arr = [3, 1, 2]

const sorted = arr.toSorted()

得到:

arr     → [3, 1, 2]
sorted  → [1, 2, 3]

这对于:

React
Vue
状态管理
函数式编程

非常友好。


toReversed()

传统:

arr.reverse()

会修改原数组。

现在:

const reversed = arr.toReversed()

原数组保持不变。


toSpliced()

传统:

arr.splice(1, 1)

直接修改原数组。

ES2023:

const result = arr.toSpliced(1, 1)

返回新数组。


with()

修改数组指定位置:

const arr = ['a', 'b', 'c']

const result = arr.with(1, 'x')

结果:

['a', 'x', 'c']

原数组:

['a', 'b', 'c']

不会改变。


findLast()

以前:

arr.find(...)

从前往后找。

现在:

arr.findLast(...)

从后往前找。

例如:

const users = [
  { name: 'Tom', active: true },
  { name: 'Jack', active: false },
  { name: 'Lucy', active: true }
]

const user = users.findLast(
  user => user.active
)

得到:

Lucy

十八、ES2024:Promise 和对象数据处理继续进化

ES2024 中非常值得关注:

Promise.withResolvers()
Object.groupBy()
Map.groupBy()
/v 正则
ArrayBuffer resize
String well-formed Unicode

Promise.withResolvers()

以前创建一个可以从外部控制的 Promise:

let resolve
let reject

const promise = new Promise((res, rej) => {
  resolve = res
  reject = rej
})

ES2024:

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

代码明显更加清晰。

这个 API 在:

事件驱动
回调转 Promise
任务队列
复杂异步控制

等场景很有用。


十九、Object.groupBy()

以前我们经常手写:

const groups = {}

for (const user of users) {
  const key = user.department

  if (!groups[key]) {
    groups[key] = []
  }

  groups[key].push(user)
}

现在:

const groups = Object.groupBy(
  users,
  user => user.department
)

例如:

const users = [
  { name: 'Tom', department: 'dev' },
  { name: 'Jack', department: 'dev' },
  { name: 'Lucy', department: 'hr' }
]

得到:

{
  dev: [
    { name: 'Tom', department: 'dev' },
    { name: 'Jack', department: 'dev' }
  ],
  hr: [
    { name: 'Lucy', department: 'hr' }
  ]
}

这对于数据处理非常实用。


Map.groupBy()

如果分组 Key 本身需要保持对象等复杂类型,可以使用:

Map.groupBy()

区别可以简单理解为:

Object.groupBy()
    → Object

Map.groupBy()
    → Map

二十、ES2025:继续补齐现代开发中的“小痛点”

ES2025 已经进入标准,里面有一些非常值得注意的 API。

例如:

RegExp.escape()
Set 新操作
Promise.try()
Iterator helpers
Float16Array

其中有些属于非常实用的工具型能力。


RegExp.escape()

以前如果需要把用户输入放进正则表达式,经常需要自己处理特殊字符:

function escapeRegExp(value) {
  return value.replace(
    /[.*+?^${}()|[\]\\]/g,
    '\\$&'
  )
}

现在:

const pattern = RegExp.escape(userInput)

再拼进正则:

const regex = new RegExp(
  RegExp.escape(userInput)
)

语义清晰很多。

特别是在:

搜索
高亮
动态正则
用户输入

这些场景中非常有价值。


Set 新增集合运算

现代 JavaScript 的 Set 越来越像真正的集合类型。

可以直接进行:

setA.union(setB)

以及:

setA.intersection(setB)

还有:

setA.difference(setB)

和:

setA.symmetricDifference(setB)

以及集合关系判断:

setA.isSubsetOf(setB)
setA.isSupersetOf(setB)
setA.isDisjointFrom(setB)

以前我们经常需要手动写循环,现在表达集合关系会更加自然。


二十一、ES2026:JavaScript 开始进一步减少“工具函数”

ES2026 是 ECMAScript 第 17 版。

这里需要特别说明:

ES2026 已经正式进入标准,但“标准已经确定”和“所有 Node.js / 浏览器版本都支持”是两个不同的问题。

ES2026 中正式进入标准的重点能力包括:

Array.fromAsync()
Error.isError()
Iterator.concat()
Map / WeakMap getOrInsert
Uint8Array Base64 / Hex
JSON.parse source access
Math.sumPrecise()

这些功能有一个共同特点:

很多以前需要自己写工具函数或者依赖第三方库的事情,现在逐渐被 JavaScript 标准库吸收。


二十二、Array.fromAsync()

如果你处理异步 Iterable:

const result = []

for await (const item of asyncIterable) {
  result.push(item)
}

ES2026 可以:

const result = await Array.fromAsync(
  asyncIterable
)

例如:

async function* getUsers() {
  yield { id: 1 }
  yield { id: 2 }
  yield { id: 3 }
}

const users = await Array.fromAsync(
  getUsers()
)

最终:

[
  { id: 1 },
  { id: 2 },
  { id: 3 }
]

对于:

AsyncIterator
异步分页
异步数据流
Node.js 流式数据

会非常方便。


二十三、Error.isError()

以前判断一个值是不是 Error,很多代码会写:

value instanceof Error

但是跨 Realm、不同执行上下文等情况下,instanceof 并不总是最理想的判断方式。

ES2026 提供:

Error.isError(value)

例如:

if (Error.isError(error)) {
  console.log(error.message)
}

语义更加明确:

判断这个值是不是一个 Error。


二十四、Map.getOrInsert()

一个非常常见的代码:

let list = map.get(key)

if (!list) {
  list = []
  map.set(key, list)
}

list.push(value)

这种代码在:

分组
缓存
索引
计数
图结构

中非常常见。

新的 API 可以简化这种模式:

const list = map.getOrInsert(key, [])

然后:

list.push(value)

这就是典型的:

Get or Insert。

它可以明显减少 Map 初始化模板代码。


二十五、Uint8Array 的 Base64 和 Hex

以前 Node.js 中处理 Base64,经常使用:

Buffer

浏览器里又经常看到:

btoa()
atob()

不同运行环境的处理方式并不统一。

ES2026 给 Uint8Array 增加了相关能力:

bytes.toBase64()

以及:

Uint8Array.fromBase64(value)

同时支持 Hex:

bytes.toHex()

和:

Uint8Array.fromHex(value)

对于:

二进制数据
文件
加密
签名
网络协议
Base64
Hex

都会比较实用。


二十六、Math.sumPrecise()

JavaScript 的浮点数一直存在经典问题:

0.1 + 0.2

得到:

0.30000000000000004

对于大量数字累加,还会遇到浮点误差累积问题。

ES2026 引入:

Math.sumPrecise()

用于对可迭代数字进行更加精确的求和。

例如:

Math.sumPrecise([
  1e17,
  1,
  -1e17
])

可以得到更符合数学预期的结果。

需要注意:

Math.sumPrecise() 解决的是浮点求和精度问题,并不意味着 JavaScript 的 Number 突然变成了任意精度小数类型。

金融场景依然需要根据具体需求考虑:

整数化
BigInt
Decimal 类库
专门的高精度方案

二十七、从 ES2016 到 ES2026,JavaScript 到底发生了什么变化?

如果把这十年压缩成一条时间线,大概是:

ES2016
    ↓
includes / **

ES2017
    ↓
async / await

ES2018
    ↓
对象展开 / async iterator

ES2019
    ↓
flat / flatMap / fromEntries

ES2020
    ↓
?. / ?? / BigInt / Promise.allSettled
    ↓
现代 JS 的重要分水岭

ES2021
    ↓
replaceAll / Promise.any / ??= / ||=

ES2022
    ↓
class fields / #private / top-level await

ES2023
    ↓
toSorted / toReversed / toSpliced / with

ES2024
    ↓
groupBy / withResolvers / 新 ArrayBuffer 能力

ES2025
    ↓
RegExp.escape / Set operations / Promise.try 等

ES2026
    ↓
Array.fromAsync / Error.isError
Map.getOrInsert / Uint8Array Base64
Math.sumPrecise 等

从这里其实可以看出一个非常明显的趋势。


二十八、JavaScript 正在从“语法糖”走向“标准库能力”

早期 JavaScript 的问题之一是:

很多常见操作都需要开发者自己封装。

例如:

数组是否包含
对象分组
对象转换
字符串替换
正则转义
Map 初始化
异步数据转换
二进制编码

于是项目里会出现大量:

utils.js
helper.js
lodash
underscore
自研工具库

而 ECMAScript 的发展方向越来越明显:

语言语法
    ↓
标准 API
    ↓
解决日常开发中的重复问题

例如以前:

indexOf() !== -1

现在:

includes()

以前:

Object.keys()
  .reduce(...)

现在:

Object.groupBy()

以前:

arr.slice().sort()

现在:

arr.toSorted()

以前:

new Promise((resolve, reject) => {
  ...
})

现在某些场景:

Promise.withResolvers()

以前:

if (!map.has(key)) {
  map.set(key, [])
}

现在:

map.getOrInsert(key, [])

JavaScript 正在不断把这些高频模式变成语言标准的一部分。


二十九、TypeScript 和 ES 标准是什么关系?

很多现代 Node.js 开发者使用:TypeScript容易产生一个误区:

TypeScript 新语法是不是 ES 新语法?

其实不是。

TypeScript 自己增加了很多类型系统能力:

interface User {
  id: number
  name: string
}

:

type UserId = string | number

这些属于 TypeScript。

而:

user?.name

:

user ?? defaultUser

属于 ECMAScript。

可以简单理解:

TypeScript
    ↓
类型系统 + JavaScript
    ↓
编译 / 转换
    ↓
JavaScript
    ↓
Node.js / Browser

所以学习 TypeScript 时,JavaScript 的 ES 标准依然是基础。


三十、结语

从 ES2016 到 ES2026,JavaScript 已经发生了非常大的变化。

如果把这十年的变化总结成几个关键词:

ES2016
    更实用的数组 API

ES2017
    异步编程革命

ES2018
    对象操作 + 异步迭代

ES2019
    数据处理能力增强

ES2020
    ?. + ?? + BigInt
    现代 JavaScript 分水岭

ES2021
    Promise 和赋值操作增强

ES2022
    类、私有字段、顶层 await

ES2023
    不修改原数组的新 API

ES2024
    数据分组和 Promise 工具

ES2025
    RegExp、Set、Promise 等进一步完善

ES2026
    异步 Iterable、Map、二进制、错误处理等能力继续标准化

真正值得学习的并不是:

ES2016 有几个 API
ES2017 有几个 API
ES2018 有几个 API

而是理解 JavaScript 这十年的演进方向:

回调
 ↓
Promise
 ↓
async / await

手写工具函数
 ↓
标准 API

修改原对象
 ↓
不可变操作

简单对象
 ↓
更强的数据处理能力

运行时差异
 ↓
更加统一的标准能力

对于现在的 Node.js / TypeScript 开发者来说,至少应该把:

async / await
?
??
Promise.allSettled
Promise.any
Object.entries
Object.fromEntries
Array.flat
Array.flatMap
Array.at
toSorted
toReversed
toSpliced
Object.groupBy
Map.groupBy
Promise.withResolvers
Set operations

这些现代能力真正用熟。

这样你再回头看几年前的 JavaScript 代码,会明显感觉到:

现在的 JavaScript,已经不是当年的 JavaScript 了。

#nodejs #前端
ZZC

ZZC

写代码,也写一些生活。