从 ES2015 到 ES2025:你还跟得上吗

ES6 是 2015 年发布的。

距离现在,已经过去整整十年。

这十年里,JavaScript 每一年都在进化。

新语法、新 API、新并发模型、新数据结构......

可大多数人,对 JavaScript 的认知,仍停留在:

  • 箭头函数
  • 解构赋值
  • Promise
  • let / const

从 ES2016 到 ES2025,你真的跟上了吗?

这篇文章,我会按时间线带你系统梳理 JavaScript 十年的演进轨迹。


ES2016 → ES2020

这5年新出的特性我想大多数人都已经熟练使用了,这里就简单列下,不详细介绍api细节了

ES2016

这是一个小版本,主要有以下3个特性:

  • Array.prototype.includes()

  • 指数运算符 (**)

    js 复制代码
    2 ** 3; // 8
  • 幂赋值运算符**=

    js 复制代码
    let num = 2; 
    num **= 3; // num = num ** 3 
    console.log(num); // 8

ES2017

ES2017的重点是异步编程,对象操作

  • async/await
  • Object.values()/Object.entries()
  • Object.getOwnPropertyDescriptors(): 返回对象所有自身属性的描述符对象
  • 字符串填充String Padding
js 复制代码
console.log('5'.padStart(3, '0')); // '005' 
console.log('hello'.padEnd(10, '*')); // 'hello*****'
  • SharedArrayBuffer 和 Atomics

    这两个用在web worker中。主线程和worker使用postMessage通信往往要将数据拷贝一份,SharedArrayBuffer 则允许 Worker 线程与主线程共享同一块内存,通过 postMessageSharedArrayBuffer 转移给 Worker,不会复制数据:

    js 复制代码
    // main.js
     const sab = new SharedArrayBuffer(1024);
     const worker = new Worker('worker.js');
     worker.postMessage(sab); //不复制数据
    js 复制代码
    // worker.js
     self.onmessage = (e) => {
       const sab = e.data; // 同一个内存块
     };

计算机中写操作可能被编译成多条指令,如果尚未写完就有其他线程读数据,便会产生错误。在多线程操作SharedArrayBuffer时就可能会出现这种问题。Atomics提供了原子级操作,其他线程读取到的数据,要么是没写入的,要么是已写完的。另外Atomics还提供了线程的阻塞和唤醒。

ES2018

ES2018新增了多个特性,算是一次中等规模升级,主要有异步编程的增强、对象和数组操作的改进、正则表达式的扩展,以及模板字面量的优化。

  • 异步生成器/异步迭代器
js 复制代码
async function* fetchPages() {
  let page = 1;
  while (page <= 3) {
    const response = await fetch(`https://api.example.com/page/${page}`);
    yield await response.json();
    page++;
  }
}

(async () => {
  for await (const data of fetchPages()) {
    console.log(data);
  }
})();
  • Promise.prototype.finally()
  • rest/spreat操作符...
  • 正则表达式增强(后行断言,命名捕获等)
  • 模板字符串的标签模板提供raw访问原始字符串
js 复制代码
function tag(strings) { 
    return strings.raw[0]; // 访问原始字符串,包括非法转义,比如LaTeX语法
} 
console.log(tag`\u{00000042}`); // strings[0]是'B' strings.raw[0]为\u{00000042}

ES2019

该版本内容不多但很实用

  • Array.prototype.flat() / flatMap()
  • Object.fromEntries()
  • String.trimStart() / trimEnd()
  • Optional catch binding : catch 可省略错误参数
js 复制代码
try {
  JSON.parse('invalid json');
} catch {
  console.log('Parsing failed'); // 无需未使用的 error 变量
}
  • Symbol.description
  • Function.prototype.toString()能返回函数精确源码,包括注释和空格,方便调试
  • 稳定的 Array.prototype.sort()

ES2020

这个版本也是一个里程碑,更新了大量内容,而且都很实用

  • BigInt
  • Dynamic Import import()
  • 空值合并运算符??
  • 可选链运算符?.
  • Promise.allSettled()
  • String.prototype.matchAll()
  • 标准全局对象globalThis
  • 模块命名空间导出(export * as ns from 'mod')
  • for-in 枚举顺序与定义顺序一致

从ES2021开始新增的特性,在我日常code review中看到的越来越少了,但很多特性还是很实用的。

ES2021

String.prototype.replaceAll()

在此之前全局替换需要用正则/g标志

逻辑赋值运算符 (&&=, ||=, ??=)

js 复制代码
let x = 0;
x ||= 10; // x = 10(因为 0 是 falsy)

let y = 5;
y &&= 20; // y = 20(因为 5 是 truthy)

let z;
z ??= 'default'; // z = 'default'(因为 undefined 是 nullish)

数字分隔符(1_000_000)

允许在数字字面量中使用下划线(_)作为分隔符,提高大数字的可读性。

Promise.any()

返回一个 Promise,在任意一个输入 Promise resolved 时解决;如果所有 rejected,则返回 AggregateError。

  • 语法:Promise.any(iterable)

    • iterable:Promise 数组或其他可迭代对象。
  • 示例

    js 复制代码
    const promises = [
      Promise.reject('Error 1'),
      Promise.resolve('Success'),
      Promise.reject('Error 2')
    ];
    Promise.any(promises)
      .then(value => console.log(value)) // 'Success'(第一个 resolved)
      .catch(error => console.error(error)); // 如果全 reject:AggregateError

WeakRefs和FinalizersRegistry:

  • WeakRefs 用于引用对象而不阻止垃圾回收
  • FinalizersRegistry 用于缓存或观察对象,而不干扰内存管理;FinalizationRegistry 提供清理回调,但不保证时序。
js 复制代码
let obj = { name: 'Alice' };
const weak = new WeakRef(obj);

const registry = new FinalizationRegistry(heldValue => {
  console.log(`Object with ${heldValue} cleaned up`);
  console.log(weak.deref());//undefined
});
registry.register(obj, 'Alice');
obj = null; // GC 时调用 callback

ES2022

Top-level await:

可以直接在模块最外层使用 await

Class的私有/静态成员和方法

增加了#标识私有,static标识静态(现在都用ts了,这两个特性很少用到)

js 复制代码
 class Counter {
  #value = 0;

  #increment() {
    this.#value++;
  }

  get #count() {
    return this.#value;
  }

  add() {
    this.#increment();
  }

  getValue() {
    return this.#count;
  }
}

const c = new Counter();
c.add();
console.log(c.getValue()); // 1
// c.#increment(); // SyntaxError,不过控制台访问不会报错
js 复制代码
class MathUtils {
  static PI = 3.14159;
  static #secret = 42;

  static getPi() {
    return this.PI;
  }

  static getSecret() {
    return this.#secret;
  }
}

console.log(MathUtils.PI); // 3.14159
console.log(MathUtils.getSecret()); // 42

Error.cause:

在 Error 对象中添加 cause 属性,用于链式记录错误上下文,方便调试。

js 复制代码
 try {
  throw new Error('Original issue');
} catch (err) {
  throw new Error('Failed operation', { cause: err });
}
// 结果错误:Failed operation (cause: Original issue)

at方法:

新增访问索引方法,可用于数组、字符串和 TypedArray

js 复制代码
const arr = [1, 2, 3];
console.log(arr.at(1)); // 2
console.log(arr.at(-1)); // 3(最后一个元素,极力推荐这种写法)
console.log('hello'.at(-2)); // 'l'

Object.hasOwn():

代替Object.prototype.hasOwnProperty.call()

正则表达式的/d标志:

可用/d标志获取匹配范围

js 复制代码
    const match = 'hello world'.matchAll(/(hello)/dg);
    for (const m of match) {
      console.log(m.indices[0]); // [0, 5] 对于 'hello'
    }

ES2023

Array 和 TypedArray的末尾查找

新增findLast() / findLastIndex()

通过拷贝修改数组(不改变原数组)

新增 toSorted() / toReversed() / toSpliced() / with()

  • 语法

    • array.toReversed():返回反转拷贝。
    • array.toSorted(compareFn):返回排序拷贝。
    • array.toSpliced(start, deleteCount, ...items):返回拼接拷贝。
    • array.with(index, value):返回替换指定索引值的拷贝。
  • 示例

    js 复制代码
    const arr = [1, 3, 2];
    console.log(arr.toSorted()); // [1, 2, 3](原 arr 不变)
    console.log(arr.toReversed()); // [2, 3, 1]
    
    console.log(arr.toSpliced(1, 1, 4)); // [1, 4, 2]
    console.log(arr.with(0, 5)); // [5, 3, 2]

Hashbang 语法

允许在 ECMAScript 文件开头使用 #!(shebang)注释,指示解释器执行脚本。

js 复制代码
#!/usr/bin/env node
console.log('Hello from Node.js');

允许 Symbols 用作 WeakMap、WeakSet 的键

此前key仅支持对象


ES2024

Promise.withResolvers()

同时创建 Promise 及其 resolve 和 reject 函数,便于手动控制 Promise 的状态。

js 复制代码
const { promise, resolve, reject } = Promise.withResolvers();

setTimeout(() => resolve('Success'), 1000);

promise.then(value => console.log(value)); // 'Success'

Object.groupBy() 和 Map.groupBy()

静态方法,用于根据回调函数返回的值对可迭代对象进行分组,返回一个对象(Object.groupBy)或 Map(Map.groupBy)。

  • 语法

    • Object.groupBy(iterable, callback)
    • Map.groupBy(iterable, callback)
  • 示例

    js 复制代码
    const items = [
      { name: 'apple', type: 'fruit' },
      { name: 'carrot', type: 'vegetable' },
      { name: 'banana', type: 'fruit' }
    ];
    
    const grouped = Object.groupBy(items, item => item.type);
    console.log(grouped); // { fruit: [{...}, {...}], vegetable: [{...}] }
    
    const groupedMap = Map.groupBy(items, item => item.type);
    console.log(groupedMap.get('fruit')); // [{...}, {...}]

正则表达式/v标志

新标志 /v 启用 Unicode 集模式,支持属性的组合、范围、否定、交集 / 并集运算

/v 标志出现前,JS 正则有 /u 标志支持基础 Unicode 属性转义 (如 \p{Letter} 匹配字母),但只能匹配 "单一属性",无法直接表达 "属性的组合 / 范围 / 否定",而 /v 标志正是为了解决这个问题,提供扩展 Unicode 属性转义能力。

  • 语法:/pattern/v

  • 示例

    js 复制代码
    const regex = /[\p{Emoji}&&\p{Emoji_Presentation}]/v;
    console.log(regex.test('😀')); // true(Emoji)
    
    // 集操作示例
    const setDiff = /[a-z--[aeiou]]/v; // 辅音
    console.log(setDiff.test('b')); // true

Atomics.waitAsync()

共享内存的异步等待方法,返回一个 Promise,在共享值变化时解决。

  • 语法:Atomics.waitAsync(array, index, value, timeout)

  • 示例

    js 复制代码
    const sab = new SharedArrayBuffer(16);
    const int32 = new Int32Array(sab);
    
    const result = Atomics.waitAsync(int32, 0, 0);
    result.value.then(() => console.log('Woken up'));
    // 其他线程:Atomics.store(int32, 0, 1); Atomics.notify(int32, 0);

ArrayBuffer 和 SharedArrayBuffer 的resize和transfer

  • 语法

    • buffer.resize(newLength)
    • buffer.transfer(newLength):返回新缓冲区,旧的被分离。
    • 类似方法可用于 SharedArrayBuffer。
  • 示例

    js 复制代码
    const buffer = new ArrayBuffer(8, { maxByteLength: 16 });
    buffer.resize(16);
    console.log(buffer.byteLength); // 16
    
    const transferred = buffer.transfer();
    // 原 buffer 被分离,无法使用

String.prototype.isWellFormed() 和 toWellFormed()

这两个api用于"格式不良"字符串.

JavaScript 字符串基于 UTF-16 编码,其中单独的代理对字符(未配对的高 / 低代理项) 属于 "格式不良" 的字符串(也叫 "畸形 UTF-16 字符串")。

高代理项范围:0xD800 - 0xDBFF

低代理项范围:0xDC00 - 0xDFFF只有高 + 低代理项配对才是合法的 UTF-16 字符,单独出现其中一个就是 "格式不良"。

js 复制代码
   // 1. 格式良好的字符串(正常字符、合法代理对)
   const validStr1 = 'Hello 世界';
   console.log(validStr1.isWellFormed()); // true

   const validStr2 = '\uD83D\uDE00'; // 😀(合法的高+低代理对)
   console.log(validStr2.isWellFormed()); // true

   // 2. 格式不良的字符串(单独的高代理项/低代理项)
   const invalidStr1 = '\uD83D'; // 单独的高代理项(无对应低代理项)
   console.log(invalidStr1.isWellFormed()); // false

   const invalidStr2 = '测试\uDC00'; // 单独的低代理项(无对应高代理项)
   console.log(invalidStr2.isWellFormed()); // false

   // 3. 格式良好的字符串:返回原字符串副本
   const validStr = 'Hello 😀';
   console.log(validStr.toWellFormed()); // Hello 😀
   console.log(validStr.toWellFormed() === validStr); // true(内容相同,引用不同)

   // 4. 格式不良的字符串:替换未配对代理项为 �
   const invalidStr1 = '\uD83D'; // 单独高代理项
   console.log(invalidStr1.toWellFormed()); // �

ES2025

迭代器助手方法(Iterator Helpers)

引入 Iterator 全局对象及其原型方法,支持对任何可迭代对象(如 Array、Set、Map)进行函数式操作,如 map、filter 等。这些方法返回新的迭代器,支持惰性求值。

  • 语法:Iterator.from(iterable).method(callback)

    • 支持方法:map()、filter()、reduce()、take()、drop()、flatMap()、toArray()、forEach() 等。
  • 示例

    js 复制代码
    const arr = [1, 2, 3, 4];
    const evenSquares = Iterator.from(arr)
      .filter(x => x % 2 === 0)
      .map(x => x ** 2)
      .toArray();
    console.log(evenSquares); // [4, 16]

Set新增方法

为 Set.prototype 添加数学集合操作方法,支持集合的并集、交集、差集等。

  • 新增方法

    • set.union(other) 并集
    • set.intersection(other) 交集
    • set.difference(other) 差集
    • set.symmetricDifference(other) 对称差集(并集减交集)
    • set.isSubsetOf(other) 子集
    • set.isSupersetOf(other) 超集
    • set.isDisjointFrom(other) 不相交
  • 示例

    js 复制代码
    const setA = new Set([1, 2, 3]);
    const setB = new Set([2, 3, 4]);
    
    console.log(setA.union(setB)); // Set {1, 2, 3, 4}
    console.log(setA.intersection(setB)); // Set {2, 3}
    console.log(setA.isSubsetOf(setB)); // false

直接导入JSON 模块

引入导入属性(with 关键字),支持直接导入 JSON 文件作为模块。

  • 语法:import json from "./data.json" with { type: "json" };

  • 示例

    js 复制代码
    import data from "./config.json" with { type: "json" };
    console.log(data); // { key: "value" }

Promise.try()

一个新的静态方法,用于包装函数调用,确保返回 Promise,无论函数是否异步或抛出错误。

  • 语法:Promise.try(callback)

  • 示例

    js 复制代码
    Promise.try(() => {
      if (Math.random() > 0.5) throw new Error('Fail');
      return 'Success';
    })
      .then(result => console.log(result))
      .catch(error => console.error(error));

新增Float16Array

引入 Float16Array 类型数组,支持 16 位浮点数,以及 DataView 的 getFloat16/setFloat16 和 Math.f16round()。

RegExp.escape() 方法

一个静态方法,用于转义字符串,使其可安全用于正则表达式。

  • 语法:RegExp.escape(str)

  • 示例

    js 复制代码
    const userInput = 'a.b*c?';
    const regex = new RegExp(RegExp.escape(userInput), 'g');
    console.log('a.b*c?'.replace(regex, 'replaced')); // 'replaced'

正则表达式内联标志

  • 语法:/(?i:case-insensitive)/

  • 示例

    js 复制代码
    const regex = /(?i:hello)/;
    console.log(regex.test('HELLO')); // true(忽略大小写)

正则表达式重复命名捕获组

允许在正则表达式中重复使用相同的命名捕获组名称。

  • 语法:/(?<group>a)|(?<group>b)/

  • 示例

    js 复制代码
    const regex = /(?<year>\d{4})-(?<month>\d{2})|(?<year>\d{4})/(?<month>\d{2})/;
    const match = '2025-07'.match(regex);
    console.log(match.groups.year); // '2025'
    console.log(match.groups.month); // '07'
相关推荐
Filotimo_1 小时前
Vue 选项式 API vs 组合式 API:区别全解析
前端·javascript·vue.js
文心快码BaiduComate2 小时前
百度文心快码全面支持GLM-5
前端·人工智能
unirst19850072 小时前
使用vite打包并部署vue项目到nginx
前端·vue.js·nginx
wordbaby2 小时前
Vue 实战:从零实现“划词标注”与“高亮笔记”功能
前端
上海合宙LuatOS2 小时前
LuatOS核心库API——【fatfs】支持FAT32文件系统
java·前端·网络·数据库·单片机·嵌入式硬件·物联网
wuhen_n2 小时前
JavaScript 手写 new 操作符:深入理解对象创建
前端·javascript
不想秃头的程序员2 小时前
TypeScript 核心基础知识
前端·面试·typescript
派星2 小时前
Day02:认识 AI IDE 工具
前端·人工智能
m0_528749002 小时前
linux编程----目录流
java·前端·数据库