从 ES2020 到 ES2024 新特性总结(写给自己看的查漏补缺)

1,677 阅读8分钟

介绍

ECMAScript 是 JavaScript 的语言规范,从 2015 年 ES6 发布以来,每年都会发布新版本。本文将介绍从 ES2020(ES11) 到 ES2024(ES15) 的主要新特性。

ES2020新特性

1. 空值合并运算符(??)

空值合并操作符 (??) 是一个逻辑操作符,当左侧的操作数为 null 或者 undefined 时,返回右侧操作数,否则返回左侧操作数。

const foo = null ?? 'default string';
console.log(foo); // 'default string'

const baz = 0 ?? 42;
console.log(baz); // 0

|| 运算符的区别:

  • || 会在左侧为假值时返回右侧操作数
  • ?? 只会在左侧为 nullundefined 时返回右侧操作数

2. 可选链操作符(?.)

可选链操作符允许我们在访问对象的属性时,如果对象是 nullundefined ,不会引起错误,而是返回 undefined

const user = {
  name: 'Kelly',
  address: {
    street: 'Main St.'
  }
};

// 以前的写法
const street1 = user && user.address && user.address.street;

// 使用可选链
const street2 = user?.address?.street;

3. Promise.allSettled()

Promise.allSettled() 方法返回一个在所有给定的 promise 都已经 fulfilledrejected 后的 promise ,并带有一个对象数组,每个对象表示对应的 promise 结果。

const promises = [
  Promise.resolve(3),
  new Promise((resolve) => setTimeout(() => resolve(1), 3000)),
  Promise.reject(new Error('fail'))
];

Promise.allSettled(promises).then((results) => {
  console.log(results);
  // [
  //   { status: "fulfilled", value: 3 },
  //   { status: "fulfilled", value: 1 },
  //   { status: "rejected", reason: Error: fail }
  // ]
});

4. BigInt

BigInt 是一种新的数据类型,用来表示任意精度的整数。通过在整数末尾附加 n 或调用 BigInt() 函数来创建。

// 创建 BigInt
const bigInt = 9007199254740991n;
const alsoHuge = BigInt(9007199254740991);

// 运算
const result = bigInt + 1n; 
console.log(result); // 9007199254740992n

// 注意:不能与普通 number 混合运算
// TypeError: Cannot mix BigInt and other types
// const invalid = bigInt + 1; 

5. globalThis

globalThis 提供了一个标准的方式来获取不同环境下的全局对象。解决了不同环境下获取全局对象的方式不统一的问题。

// 以前在不同环境需要这样获取全局对象
const getGlobal = function() {
  if (typeof self !== 'undefined') return self;
  if (typeof window !== 'undefined') return window;
  if (typeof global !== 'undefined') return global;
  throw new Error('unable to locate global object');
};

// 现在统一使用 globalThis
console.log(globalThis); // 指向全局对象

6. String.prototype.matchAll()

matchAll() 方法返回一个包含所有匹配正则表达式的结果及其分组捕获组的迭代器。

const regexp = /t(e)(st(\d?))/g;
const str = 'test1test2';

const matches = [...str.matchAll(regexp)];
console.log(matches);
// [
//   ['test1', 'e', 'st1', '1'],
//   ['test2', 'e', 'st2', '2']
// ]

7. dynamic import()

动态 import() 语法允许我们按需导入模块。

// 静态导入
import { add } from './math.js';

// 动态导入
import('./math.js')
  .then(module => {
    console.log(module.add(1, 2));
  })
  .catch(err => {
    console.log('Loading module failed');
  });

ES2021新特性

1. String.prototype.replaceAll()

replaceAll() 方法返回一个新字符串,所有符合匹配规则的字符都将被替换。

const str = 'hello hello hello';
console.log(str.replaceAll('hello', 'hi')); // 'hi hi hi'

// 之前的替代方案
console.log(str.replace(/hello/g, 'hi')); // 'hi hi hi'

2. Promise.any()

Promise.any() 接收一个 Promise 可迭代对象,只要其中的一个 promise 成功,就返回那个已经成功的 promise。如果所有的 promise 都失败,则返回一个失败的 promise。

const promises = [
  Promise.reject('Error 1'),
  Promise.resolve('Success'),
  Promise.reject('Error 2')
];

Promise.any(promises).then((value) => {
  console.log(value); // 'Success'
});

// 当所有Promise都失败时
Promise.any([
  Promise.reject('Error 1'),
  Promise.reject('Error 2')
]).catch((error) => {
  console.log(error); // AggregateError: All promises were rejected
});

3. 逻辑赋值运算符

新增了三个逻辑赋值运算符:??=&&=||=

逻辑空赋值(??=)

??= 等价于 x = x ?? 'default';

let x = null;
x ??= 'default';
console.log(x); // 'default'

逻辑与赋值(&&=)

&&= 等价于 y = y && 2;

let y = 1;
y &&= 2;
console.log(y); // 2

逻辑或赋值(||=)

||= 等价于 z = z || 3;

let z = 0;
z ||= 3;
console.log(z); // 3

4. 数字分隔符

数字分隔符 _ 允许在数字之间创建可视化分隔符,提高数字的可读性。

const largeNumber = 1_000_000_000;
console.log(largeNumber); // 1000000000

const bytes = 0xFF_FF_FF_FF;
console.log(bytes); // 4294967295

const binary = 0b1010_0001_1000_0101;
console.log(binary); // 41349

5. WeakRefs

WeakRef 实例可以让你保持对另一个对象的弱引用,而不会阻止该对象被垃圾回收。

const target = { name: 'target' };
const weakRef = new WeakRef(target);

// 获取目标对象,如果目标对象已被回收则返回 undefined
console.log(weakRef.deref()); // { name: 'target' }

ES2022新特性

1. Class Fields

ES2022 为类增加了私有字段、私有方法和静态初始化块等特性。

class Counter {
  // 公共字段
  count = 0;
  
  // 私有字段
  #privateValue = 42;
  
  // 私有方法
  #increment() {
    this.count++;
    this.#privateValue++;
  }
  
  // 静态公共字段
  static baseCount = 0;
  
  // 静态私有字段
  static #instances = 0;
  
  // 静态初始化块
  static {
    this.#instances++;
  }
}

不过很遗憾,在实际 Chrome 浏览器中的 131.0.6778.205 版本,这个特性并没有生效。

2. RegExp Match Indices

为正则表达式添加了 /d 标志,它会生成一个包含开始和结束索引的额外属性。

const re = /test/d;
const result = re.exec('This is a test message');

console.log(result.indices[0]); // [10, 14]
// 10 是匹配开始的位置
// 14 是匹配结束的位置

3. Top-level await

现在可以在模块的顶层使用 await,而不需要包装在 async 函数中。

// 以前需要这样
async function initialize() {
  const response = await fetch('https://api.example.com/data');
  const data = await response.json();
  return data;
}

// 现在可以直接在模块顶层使用
const response = await fetch('https://api.example.com/data');
const data = await response.json();
export { data };

4. Object.hasOwn()

Object.hasOwn() 是一个新的静态方法,用于替代 Object.prototype.hasOwnProperty()

const object = {
  prop: 'exists'
};

// 旧方法
console.log(object.hasOwnProperty('prop')); // true

// 新方法
console.log(Object.hasOwn(object, 'prop')); // true

5. at()方法

数组和字符串新增了 at() 方法,支持使用负数索引从末尾访问元素。

const array = [1, 2, 3, 4, 5];

// 传统方式访问最后一个元素
console.log(array[array.length - 1]); // 5

// 使用at()方法
console.log(array.at(-1)); // 5
console.log(array.at(-2)); // 4

const str = 'Hello';
console.log(str.at(-1)); // 'o'

6. Error Cause

Error 对象新增了 cause 属性,用于指定错误的原因。

try {
  throw new Error('Failed to fetch data', { 
    cause: 'Network error' 
  });
} catch (error) {
  console.log(error.cause); // 'Network error'
}

ES2023新特性

1. Array.prototype.toReversed()

返回数组的反转副本,不修改原数组。

const numbers = [1, 2, 3, 4, 5];
const reversed = numbers.toReversed();

console.log(reversed); // [5, 4, 3, 2, 1]
console.log(numbers); // [1, 2, 3, 4, 5] - 原数组未被修改

2. Array.prototype.toSorted()

返回数组的排序副本,不修改原数组。

const fruits = ['banana', 'apple', 'orange'];
const sorted = fruits.toSorted();

console.log(sorted); // ['apple', 'banana', 'orange']
console.log(fruits); // ['banana', 'apple', 'orange'] - 原数组未被修改

3. Array.prototype.toSpliced()

返回一个新数组,该数组包含了删除和/或添加的元素,不修改原数组。

const colors = ['red', 'green', 'blue'];
const newColors = colors.toSpliced(1, 1, 'yellow', 'purple');

console.log(newColors); // ['red', 'yellow', 'purple', 'blue']
console.log(colors); // ['red', 'green', 'blue'] - 原数组未被修改

4. Array.prototype.with()

返回一个新数组,其中指定索引的元素被替换为给定值。

const numbers = [1, 2, 3, 4, 5];
const newNumbers = numbers.with(2, 6);

console.log(newNumbers); // [1, 2, 6, 4, 5]
console.log(numbers); // [1, 2, 3, 4, 5] - 原数组未被修改

5. Array.prototype.findLast()

从后向前查找数组中满足条件的第一个元素。

const numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
const lastEven = numbers.findLast(num => num % 2 === 0);

console.log(lastEven); // 2

6. Array.prototype.findLastIndex()

从后向前查找数组中满足条件的第一个元素的索引。

const numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
const lastEvenIndex = numbers.findLastIndex(num => num % 2 === 0);

console.log(lastEvenIndex); // 7

7. Hashbang Grammar

支持在JavaScript文件开头使用Hashbang注释。

#!/usr/bin/env node

console.log('Hello, World!');

ES2024新特性

1. RegExp v flag

新增了 v 标志,用于启用更符合 Unicode 规范的字符串匹配行为。

// 使用v标志可以正确匹配Unicode属性
const re = /\p{Decimal_Number}/v;
console.log(re.test('𝟏')); // true

// 支持集合操作
const pattern = /[\p{ASCII}&&\p{Decimal_Number}]/v;
console.log(pattern.test('5')); // true
console.log(pattern.test('七')); // false

2. String.prototype.isWellFormed()

检查字符串是否包含有效的 UTF-16 编码序列。

const string1 = 'hello';
console.log(string1.isWellFormed()); // true

// 包含孤立的代理对
const string2 = '\uD800';
console.log(string2.isWellFormed()); // false

3. String.prototype.toWellFormed()

返回一个新字符串,其中所有无效的 UTF-16 编码序列都被替换为 Unicode 替换字符(\uFFFD)。

const string = '\uD800A';
console.log(string.toWellFormed()); // 'A'

4. Promise.withResolvers()

提供了一种更简洁的方式来创建 Promise 和获取其解析器。

// 旧方式
let resolve, reject;
const promise = new Promise((res, rej) => {
  resolve = res;
  reject = rej;
});

// 新方式
const { promise, resolve, reject } = Promise.withResolvers();

// 使用示例
resolve('success');
promise.then(value => console.log(value)); // 'success'

5. Temporal API

提供了一个更现代化的日期和时间 API,用于替代现有的 Date 对象。

// 创建日期
const date = new Temporal.PlainDate(2024, 3, 15);
console.log(date.toString()); // '2024-03-15'

// 创建时间
const time = new Temporal.PlainTime(13, 30, 15);
console.log(time.toString()); // '13:30:15'

// 日期计算
const futureDate = date.add({ days: 7 });
console.log(futureDate.toString()); // '2024-03-22'

6. RegExp Modifiers

允许在正则表达式执行期间修改标志。

const pattern = /foo/ig;
const newPattern = pattern.withModifiers('d');
console.log(newPattern.flags); // 'gid'

7. Import Attributes

允许在导入语句中指定额外的模块属性。

// 导入JSON模块
import data from './data.json' with { type: 'json' };

// 导入WebAssembly模块
import module from './module.wasm' with { type: 'webassembly' };

总结

从 ES2020 到 ES2024 , ECMAScript 增加了许多实用的新特性:

  1. ES2020 引入了空值合并运算符和可选链操作符,大大简化了空值处理;
  2. ES2021 带来了 Promise.any() 和字符串 replaceAll() 等便利功能;
  3. ES2022 实现了类字段的私有属性和顶层 await 等重要特性;
  4. ES2023 为数组操作提供了一系列不可变方法;
  5. ES2024 则带来了全新的 Temporal API 和 Promise.withResolvers() 等重要更新;

参考资料

  1. ECMAScript 2024 规范
  2. TC39 提案
  3. MDN Web Docs