一、ES11新特性(2020)

ES2020新特性是ECMAScript对应2020年的版本。通过babel中的babel-preset-es2020可以使用。

1、BigInt

(1)之前存在的问题

JavaScript 所有数字都保存成 64 位浮点数,这给数值的表示带来了两大限制。一是数值的精度只能到 53 个二进制位(相当于 16 个十进制位),大于这个范围的整数,JavaScript 是无法精确表示的,这使得 JavaScript 不适合进行科学和金融方面的精确计算。二是大于或等于2的1024次方的数值,JavaScript 无法表示,会返回Infinity。

1
2
3
4
5
6
7
8
9
10
11
// 1. 超过 53 个二进制位的数值,无法保持精度
Math.pow(2, 53) === Math.pow(2, 53) + 1 // true

// 2. 超过 2 的 1024 次方的数值,无法表示
Math.pow(2, 1024) // Infinity

// 3. 最大安全整数
let max = Number.MAX_SAFE_INTEGER;
let max1 = max + 1
let max2 = max + 2
max1 === max2 // true

(2)引入BigInt后

现在引入了一种新的数据类型 BigInt(大整数),来解决这个问题。BigInt 只用来表示整数,没有位数的限制,任何位数的整数都可以精确表示。

创建 BigInt 类型的值也非常简单,只需要在数字后面加上 n 即可。例如,123 变为 123n。也可以使用全局方法 BigInt(value) 转化,入参 value 为数字或数字字符串。

1
2
3
4
5
6
let max = BigInt(Number.MAX_SAFE_INTEGER);

let max1 = max + 1n
let max2 = max + 2n

max1 === max2 // false

(3)判断类型:

1
2
3
typeof 1n === 'bigint'; // true
typeof BigInt('1') === 'bigint'; // true
Object.prototype.toString.call(10n) === '[object BigInt]'; // true

(4)BigInt和Number不严格相等,但是宽松相等:

1
2
10n === 10 // false
10n == 10 // true

(5)Number和BigInt可以进行比较

1
2
3
4
5
1n < 2;    // true
2n > 1; // true
2 > 2; // false
2n > 2; // false
2n >= 2; // true

(6)如果算上 BigInt,JavaScript 中原始类型就从 6 个变为了 7 个。

  • Boolean
  • Null
  • Undefined
  • Number
  • String
  • Symbol (new in ECMAScript 2015)
  • BigInt (new in ECMAScript 2019)

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

1
2
3
a?.[x]
// 等同于
a == null ? undefined : a[x]

3、Promise.allSettled

Promise.allSettledPromise.all类似, 其参数接受一个Promise的数组, 返回一个新的Promise, 唯一的不同在于, 它不会进行短路, 也就是说当Promise全部处理完成后,我们可以拿到每个Promise的状态, 而不管是否处理成功。

4、Dynamic import

现在前端打包资源越来越大,前端应用初始化时根本不需要全部加载这些逻辑资源,为了首屏渲染速度更快,很多时候都是动态导入(按需加载)模块,比如懒加载图片等,这样可以帮助您提高应用程序的性能。

import()可以用于script脚本中,import(module) 函数可以在任何地方调用。它返回一个解析为模块对象的 promise。支持 await 关键字。

5、globalThis

globalThis 目的就是提供一种标准化方式访问全局对象,有了 globalThis 后,你可以在任意上下文,任意时刻都能获取到全局对象。

如果您在浏览器上,globalThis将为window,如果您在Node上,globalThis则将为global。因此,不再需要考虑不同的环境问题。

6、class的私有变量

最新提案之一是在类中添加私有变量的方法。我们将使用 # 符号表示类的私有变量。这样就不需要使用闭包来隐藏不想暴露给外界的私有变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Counter {
#x = 0;

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

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


const c = new Counter();
c.onClick(); // 正常
c.#increment(); // 报错

通过 # 修饰的成员变量或成员函数就成为了私有变量,如果试图在 Class 外部访问,则会抛出异常。

7、空值合并操作符(??)

我们在开发过程中,经常会遇到这样场景:变量如果是空值,则就使用默认值,我们是这样实现的:

1
2
let c = a ? a : b // 方式1
let c = a || b // 方式2

这两种方式有个明显的弊端,它都会覆盖所有的假值,如(0, ‘’, false),这些值可能是在某些情况下有效的输入。

为了解决这个问题,有人提议创建一个nullish合并运算符,用 ?? 表示。有了它,我们仅在第一项为 null 或 undefined 时设置默认值。

8、class的static

它允许类拥有静态字段,类似于大多数OOP语言。静态字段可以用来代替枚举,也可以用于私有字段。

1
2
3
4
5
6
7
8
9
10
11
class Colors {
// public static 字段
static red = '#ff0000';
static green = '#00ff00';

// private static 字段
static #secretColor = '#f0f0f0';
}

font.color = Colors.red;
font.color = Colors.#secretColor; // 出错

9、String.prototype.matchAll

matchAll() 是新增的字符串方法,它返回一个包含所有匹配正则表达式的结果及分组捕获组的迭代器。因为返回的是遍历器,所以通常使用for...of循环取出。

1
2
3
4
5
for (const match of 'abcabc'.matchAll(/a/g)) {
console.log(match)
}
//["a", index: 0, input: "abcabc", groups: undefined]
//["a", index: 3, input: "abcabc", groups: undefined]

需要注意,该方法的第一个参数是一个正则表达式对象,如果传的参数不是一个正则表达式对象,则会隐式地使用 new RegExp(obj) 将其转换为一个 RegExp 。另外,RegExp必须是设置了全局模式g的形式,否则会抛出异常 TypeError。

和match()的区别就在于match()只返回匹配到的对应的值。

二、ES12新特性(2021)

2021年3月13日,ES2021 候选提案发布了其最终功能集的版本。如果它能够在今年6月的ECMA 大会上通过,就会成为官方的标准!

这个候选提案提及到ECMAScript新特性如下所示:

  • String.prototype.replaceAll()
  • Promise.any
  • 逻辑运算符和赋值表达式
  • 数值分隔符
  • WeakRef and Finalizers

这些新的特性已经进入第四阶段且已添加到谷歌 Chrome V8 引擎中。接下来我们来介绍下这些ES2021的新特性吧。

1、replaceAll()

1
2
3
let string = 'hello world, hello ES12'
string.replace(/hello/g,'hi') // hi world, hi ES12
string.replaceAll('hello','hi') // hi world, hi ES12

是对replace的扩充。注意:replaceAll在使用正则表达式的时候,如果非全局匹配(/g),会抛出异常:

1
2
3
4
let string = 'hello world, hello ES12'
string.replaceAll(/hello/,'hi')

// Uncaught TypeError: String.prototype.replaceAll called with a non-global

2、Promise.any()

在 ES6 中引入了 Promise.race() 和 Promise.all() 方法,ES2020 加入了 Promise.allSettled()。 ES2021 又增加了一个使 Promise 处理更加容易的方法:Promise.any()

Promise.any() 接收一个Promise可迭代对象,只要其中的一个 promise 成功,就返回那个已经成功的 promise 。如果可迭代对象中没有一个 promise 成功(即所有的 promise 都失败/拒绝),就返回一个失败的 promise 和AggregateError类型的实例,它是 Error的一个子类,用于把单一的错误集合在一起。

Promise.any()Promise.race()方法很像,只有一点不同,就是不会因为某个 Promise 变成rejected状态而结束。

3、逻辑赋值操作符(&&=, ||=, ??=)

1
2
3
4
var x = 1;
var y = 2;
x &&= y; // 等价于 x && (x=y)
console.log(x); // 2

4、数字分隔符

我们将通过使用_(下划线)字符在数字组之间提供分隔,使读取数值更加容易,提高可读性。

1
let x = 100_000; // 100000

5、WeakRef and Finalizers

此功能包含两个高级对象 WeakRef 和 FinalizationRegistry。根据使用情况,这些接口可以单独使用,也可以一起使用。官方建议不要轻易使用 WeakRef 和 finalizer。其中一个原因是它们可能不可预测,另一个是它们并没有真正帮 gc 完成工作,实际上可能会垃圾回收的工作更加困难。

在 JavaScript 中,当你创建了一个创建对象的引用时,这个引用可以防止对象被垃圾收集,也就是说 JavaScript 无法删除对象并释放其内存。只要对该对象的引用一直存在,就可以使这个对象永远存在。

ES2021 添加了新的类 WeakRefs。允许创建对象的弱引用。这样就能够在跟踪现有对象时不会阻止对其进行垃圾回收。对于缓存和对象映射非常有用。

必须用 new关键字创建新的 WeakRef ,并把某些对象作为参数放入括号中。当你想读取引用(被引用的对象)时,可以通过在弱引用上调用 deref() 来实现。

1
2
3
4
5
6
7
8
9
10
const myWeakRef = new WeakRef({
name: '星野',
year: '25'
})

myWeakRef.deref()
// => { name: '星野', year: '25' }

myWeakRef.deref().name
// => '星野'

与 WeakRef 紧密相连的还有另一个功能,名为 finalizers 或 FinalizationRegistry。这个功能允许你注册一个回调函数,这个回调函数将会在对象被垃圾回收时调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 创建 FinalizationRegistry:
const reg = new FinalizationRegistry((val) => {
console.log(val)
})


(() => {
// 创建新对象:
const obj = {}

//为 “obj” 对象注册 finalizer:
//第一个参数:要为其注册 finalizer 的对象。
//第二个参数:上面定义的回调函数的值。
reg.register(obj, 'obj has been garbage-collected.')
})()
// 当 "obj" 被垃圾回收时输出:
// 'obj has been garbage-collected.'

总而言之,JavaScript 中对象的引用是强引用,WeakMap 和 WeakSet 可以提供部分的弱引用功能,若想在 JavaScript 中实现真正的弱引用,可以通过配合使用 WeakRef 和终结器(Finalizer)来实现。

三、ES13新特性(2022)

1、Object.hasOwn()

在ES2022之前,可以使用 Object.prototype.hasOwnProperty() 来检查一个属性是否属于对象。

Object.hasOwn 特性是一种更简洁、更可靠的检查属性是否直接设置在对象上的方法:

1
2
3
4
5
6
7
const example = {
property: '123'
};


console.log(Object.prototype.hasOwnProperty.call(example, 'property'));
console.log(Object.hasOwn(example, 'property'));

2、通过at()来对字符串或数组进行索引

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

console.log(array[array.length-1]); // 5
console.log(array.at(-1)); // 5

console.log(array[array.lenght-2]); // 4
console.log(array.at(-2)); // 4
1
2
3
4
const str = "hello world";

console.log(str[str.length - 1]); // d
console.log(str.at(-1)); // d

3、error.cause

在ES13中,new Error()中可以指定导致它的原因:

1
2
3
4
5
6
7
8
9
10
11
12
13
function readFiles(filePaths) {
return filePaths.map(
(filePath) => {
try {
// ···
} catch (error) {
throw new Error(
`While processing ${filePath}`,
{cause: error}
);
}
});
}

4、Top-level await

ES2017(ES8)中的 async/await特性仅仅允许在 async 函数内使用 await 关键字,新的提案旨在允许 await 关键字在顶层内容中的使用,例如可以简化动态模块加载的过程:

1
const strings = await import(`/i18n/${navigator.language}`);

这个特性在浏览器控制台中调试异步内容(如 fetch)非常有用,而无需将其包装到异步函数中。

另一个使用场景是,可以在以异步方式初始化的 ES 模块的顶层使用它(比如建立数据库连接)。当导入这样的“异步模块”时,模块系统将等待它被解析,然后再执行依赖它的模块。这种处理异步初始化方式比当前返回一个初始化promise并等待它解决来得更容易。一个模块不知道它的依赖是否异步。

1
2
3
4
5
6
// db.mjs
export const connection = await createConnection();

// server.mjs
import { connection } from './db.mjs';
server.start();

在此示例中,在server.mjs中完成连接之前不会执行任何操作db.mjs

顶级await在以下场景中将非常有用:

  • 动态加载模块

    1
    const strings = await import(`/i18n/${navigator.language}`);
  • 资源初始化

    1
    const connection = await dbConnector();
  • 依赖回退

    1
    2
    3
    4
    5
    6
    let translations;
    try {
    translations = await import('https://app.fr.json');
    } catch {
    translations = await import('https://fallback.en.json');
    }

5、正则表达式匹配索引

(1)该特性允许我们利用 d 字符来表示我们想要匹配字符串的开始和结束索引。以前,只能在字符串匹配操作期间获得一个包含提取的字符串和索引信息的数组。在某些情况下,这是不够的。因此,在这个规范中,如果设置标志 /d,将额外获得一个带有开始和结束索引的数组。

1
2
3
4
const matchObj = /(a+)(b+)/d.exec('aaaabb');

console.log(matchObj[1]) // 'aaaa'
console.log(matchObj[2]) // 'bb'

(2)由于 /d 标识的存在,matchObj还有一个属性.indices,它用来记录捕获的每个编号组:

1
2
console.log(matchObj.indices[1])  // [0, 4]
console.log(matchObj.indices[2]) // [4, 6]

(3)我们还可以使用命名组:

这里给两个字符匹配分别命名为as和bs,然后就可以通过groups来获取到这两个命名分别匹配到的字符串。

它们的索引存储在 matchObj.indices.groups 中:

1
2
console.log(matchObj.indices.groups.as);  // [0, 4]
console.log(matchObj.indices.groups.bs); // [4, 6]

匹配索引的一个重要用途就是指向语法错误所在位置的解析器。