State Of Js 2022 中的 ES 语言特性

报告地址:https://2022.stateofjs.com/en-US/

Proxies

mdn文档说明

用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。

Proxy 现在还是挺常用的,比如 Vue3、和 Reflect 配合使用元编程

1
2
3
4
5
6
7
8
9
10
const handler = {
get: function (target, name) {
return name in target ? target[name] : 42;
},
};

const p = new Proxy({}, handler);
p.a = 1;

console.log(p.a, p.b); // 1, 42

趋势

p-proxies.png

趋势有点奇怪,2021 年及之前了解人数/使用人数比例还在逐年上升,2022 年居然有所减少。

Promise.allSettled()

mdn文档说明

Promise.allSettled() 方法以 promise 组成的可迭代对象作为输入,并且返回一个 Promise 实例。当输入的所有 promise 都已敲定时(包括传递空的可迭代类型),返回的 promise 将兑现,并带有描述每个 promsie 结果的对象数组。

看起来感觉跟Promise.all()很像,但Promise.allSettled() 的最大不同点在于Promise.allSettled() 永远不会被 reject。

在使用 Promise.all()时,如果有一个 promise 出现了异常,被 reject 了,就不会走到.then,如:

1
2
3
4
5
6
7
8
9
const promises = [Promise.resolve(1), Promise.resolve(2), Promise.reject(3)];

Promise.all(promises).then(values => console.log(values));
// 最终输出(Error): Uncaught (in promise) 3

Promise.all(promises)
.then(values => console.log(values))
.catch(err => console.log(err));
// 加入catch语句后,最终输出:3

这种情况下,Promise.all()的关键问题在于:尽管能用 catch 捕获其中的异常,但你会发现其他执行成功的 promise 的消息都丢失了。

Promise.allSettled不一样:

1
2
3
4
5
6
7
8
9
const promises = [Promise.resolve(1), Promise.resolve(2), Promise.reject(3)];

Promise.allSettled(promises).then(values => console.log(values));
// 最终输出:
// [
// {status: "fulfilled", value: 1},
// {status: "fulfilled", value: 2},
// {status: "rejected", value: 3},
// ]

可以看到所有 promise 的数据都被包含在 then 语句中,且每个 promise 的返回值多了一个 status 字段

兼容性

p-promise_com.png

polyfill:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
if (Promise && !Promise.allSettled) {
Promise.allSettled = function (promises) {
return Promise.all(
promises.map(function (promise) {
return promise
.then(function (value) {
return { state: 'fulfilled', value: value };
})
.catch(function (reason) {
return { state: 'rejected', reason: reason };
});
})
);
};
}

趋势

p-promise

总体来看这几年使用有所上升

Dynamic Import

mdn文档说明

关键字 import 可以像调用函数一样来动态的导入模块。以这种方式调用,将返回一个 promise。

1
2
3
import('/modules/my-module.js').then(module => {
// Do something with the module.
});

这种使用方式也支持 await 关键字。

1
2
3
const module = await import('/modules/my-module.js');

// ...

兼容性

p-dimport_com

趋势

p-dimport_com

Private Fields

mdn文档说明

类属性在默认情况下是公有的,但可以使用增加哈希前缀 # 的方法来定义私有类字段,这一隐秘封装的类特性由 js 自身强制执行。如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class ClassWithPrivateField {
#privateField;
}

class ClassWithPrivateMethod {
#privateMethod() {
return 'hello world';
}
}

class ClassWithPrivateStaticField {
static #PRIVATE_STATIC_FIELD;
}

class ClassWithPrivateStaticMethod {
static #privateStaticMethod() {
return 'hello world';
}
}

从作用域之外引用 # 名称、内部在未声明的情况下引用私有字段、或尝试使用 delete 移除声明的字段都会抛出语法错误。如:

1
2
3
4
5
6
7
8
9
10
11
12
13
class ClassWithPrivateField {
#privateField;

constructor() {
this.#privateField = 42;
delete this.#privateField; // 语法错误
this.#undeclaredField = 444; // 语法错误
}
}


const instance = new ClassWithPrivateField()
instance.#privateField === 42; // 语法错误

并且类似于公有字段,私有字段在构造(construction)基类或调用子类的 super() 方法时被添加到类实例中。

私有实例方法

1
2
3
4
5
6
7
8
9
10
11
12
13
class ClassWithPrivateMethod {
#privateMethod() {
return 'hello world';
}

getPrivateMessage() {
return this.#privateMethod();
}
}

const instance = new ClassWithPrivateMethod();
console.log(instance.getPrivateMessage());
// hello world

兼容性

私有性还是比较好做 polyfill 的,比如:

1
2
3
4
5
6
7
8
9
10
11
class Test {
#num = 1;

set setNum(num) {
this.#num = num;
}

get getNum() {
return this.#num;
}
}

babel:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
function _classPrivateFieldInitSpec(obj, privateMap, value) {
_checkPrivateRedeclaration(obj, privateMap);
privateMap.set(obj, value);
}
function _checkPrivateRedeclaration(obj, privateCollection) {
if (privateCollection.has(obj)) {
throw new TypeError('Cannot initialize the same private elements twice on an object');
}
}
function _classPrivateFieldGet(receiver, privateMap) {
var descriptor = _classExtractFieldDescriptor(receiver, privateMap, 'get');
return _classApplyDescriptorGet(receiver, descriptor);
}
function _classApplyDescriptorGet(receiver, descriptor) {
if (descriptor.get) {
return descriptor.get.call(receiver);
}
return descriptor.value;
}
function _classPrivateFieldSet(receiver, privateMap, value) {
var descriptor = _classExtractFieldDescriptor(receiver, privateMap, 'set');
_classApplyDescriptorSet(receiver, descriptor, value);
return value;
}
function _classExtractFieldDescriptor(receiver, privateMap, action) {
if (!privateMap.has(receiver)) {
throw new TypeError('attempted to ' + action + ' private field on non-instance');
}
return privateMap.get(receiver);
}
function _classApplyDescriptorSet(receiver, descriptor, value) {
if (descriptor.set) {
descriptor.set.call(receiver, value);
} else {
if (!descriptor.writable) {
throw new TypeError('attempted to set read only private field');
}
descriptor.value = value;
}
}
var _num = /*#__PURE__*/ new WeakMap();
class Test {
constructor() {
_classPrivateFieldInitSpec(this, _num, {
writable: true,
value: 1,
});
}
set setNum(num) {
_classPrivateFieldSet(this, _num, num);
}
get getNum() {
return _classPrivateFieldGet(this, _num);
}
}

趋势

p-private

逐渐上升。

Nullish Coalescing

mdn文档说明

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

与逻辑或运算符(||)不同,逻辑或运算符会在左侧操作数为假值时返回右侧操作数。也就是说,如果使用 || 来为某些变量设置默认值,可能会遇到意料之外的行为。比如为假值(例如,''0)时。见下面的例子。

1
2
3
4
5
6
7
const foo = null ?? 'default string';
console.log(foo);
// Expected output: "default string"

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

兼容性

p-nullish_com

兼容处理也很简单,polyfill 如:

1
2
3
function getNum(num) {
return num ?? 1;
}

babel:

1
2
3
function getNum(num) {
return num !== null && num !== void 0 ? num : 1;
}

趋势

p-nullish

还算是在上升。

Numeric Separators

mdn文档说明

增强数字可读性的分隔符_

1
2
3
4
5
6
1_000_000_000_000;
1_050.95;
0b1010_0001_1000_0101;
0o2_2_5_6;
0xa0_b0_c0;
1_000_000_000_000_000_000_000n;

兼容性

p-number_com.png

babel 处理时去掉分隔符就好了。

趋势

p-number

有所上升。

String.prototype.replaceAll()

mdn文档说明

1
2
3
4
5
6
7
8
9
const p = 'The quick brown fox jumps over the lazy dog. If the dog reacted, was it really lazy?';

console.log(p.replaceAll('dog', 'monkey'));
// Expected output: "The quick brown fox jumps over the lazy monkey. If the monkey reacted, was it really lazy?"

// Global flag required when calling replaceAll with regex
const regex = /Dog/gi;
console.log(p.replaceAll(regex, 'ferret'));
// Expected output: "The quick brown fox jumps over the lazy ferret. If the ferret reacted, was it really lazy?"

兼容性

p-replaceAll_com

Corejs 的 polyfill 可见https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/es.string.replace-all.js

趋势

p-replaceAll

有所上升。

String.prototype.matchAll()

mdn文档说明

1
2
3
4
5
6
7
8
9
10
const regexp = /t(e)(st(\d?))/g;
const str = 'test1test2';

const array = [...str.matchAll(regexp)];

console.log(array[0]);
// Expected output: Array ["test1", "e", "st1", "1"]

console.log(array[1]);
// Expected output: Array ["test2", "e", "st2", "2"]

兼容性

p-matchAll_com

Corejs 的 polyfill 可见https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/es.string.match-all.js

趋势

p-matchAll

有所上升。

Logical Assignment

mdn文档说明

  • Logical OR assignment: (x ||= y)
  • Logical AND assignment: (x &&= y)

如:

1
2
3
4
5
6
7
8
9
10
let a = 1;
let b = 0;

a &&= 2;
console.log(a);
// Expected output: 2

b &&= 2;
console.log(b);
// Expected output: 0
1
2
3
4
5
6
7
let x = 0;
let y = 1;

x &&= 0; // 0
x &&= 1; // 0
y &&= 1; // 1
y &&= 0; // 0

兼容性

p-logical_com

趋势

p-logical

有所上升。

Promise.any()

mdn文档说明

Promise.any() 接收一个由 Promise 所组成的可迭代对象,该方法会返回一个新的 promise,一旦可迭代对象内的任意一个 promise 变成了兑现状态,那么由该方法所返回的 promise 就会变成兑现状态,并且它的兑现值就是可迭代对象内的首先兑现的 promise 的兑现值。如果可迭代对象内的 promise 最终都没有兑现(即所有 promise 都被拒绝了),那么该方法所返回的 promise 就会变成拒绝状态,并且它的拒因会是一个 AggregateError 实例,这是 Error 的子类,用于把单一的错误集合在一起。

1
2
3
4
5
6
7
8
9
const promise1 = Promise.reject(0);
const promise2 = new Promise(resolve => setTimeout(resolve, 100, 'quick'));
const promise3 = new Promise(resolve => setTimeout(resolve, 500, 'slow'));

const promises = [promise1, promise2, promise3];

Promise.any(promises).then(value => console.log(value));

// Expected output: "quick"

返回值

  • 如果传入了一个空的可迭代对象,那么就会返回一个已经被拒的 promise
  • 如果传入了一个不含有 promise 的可迭代对象,那么就会返回一个异步兑现的 promise
  • 其余情况下都会返回一个处于等待状态的 promise。如果可迭代对象中的任意一个 promise 兑现了,那么这个处于等待状态的 promise 就会异步地(调用栈为空时)切换至兑现状态。如果可迭代对象中的所有 promise 都被拒绝了,那么这个处于等待状态的 promise 就会异步地切换至被拒状态。

兼容性

p-promiseAny_com

Corejs 的 polyfill 可见https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/es.promise.any.js

趋势

p-promiseAny

居然有所降低。

Array.prototype.at()

mdn文档说明

at() 方法接收一个整数值并返回该索引对应的元素,允许正数和负数。负整数从数组中的最后一个元素开始倒数。

1
2
3
4
5
6
7
8
9
10
11
const array1 = [5, 12, 8, 130, 44];

let index = 2;

console.log(`Using an index of ${index} the item returned is ${array1.at(index)}`);
// Expected output: "Using an index of 2 the item returned is 8"

index = -2;

console.log(`Using an index of ${index} item returned is ${array1.at(index)}`);
// Expected output: "Using an index of -2 item returned is 130"

参数: index, {String}

  • 要返回的数组元素的索引(位置)。当传递负数时,支持从数组末端开始的相对索引;也就是说,如果使用负数,返回的元素将从数组的末端开始倒数。

返回值

  • 匹配给定索引的数组中的元素。如果找不到指定的索引,则返回 undefined

兼容性

p-arrayAt_com

Corejs 的 polyfill 可见https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/es.array.at.js

趋势

p-arrayAt

呈上升趋势。

Top Level await()

mdn文档说明

在模块的顶层,你可以单独使用关键字 await(异步函数的外面)。也就是说一个模块如果包含用了 await 的子模块,该模块就会等待该子模块,这一过程并不会阻塞其它子模块。

下面是一个在 export 表达式中使用了 Fetch API 的例子。任何文件只要导入这个模块,后面的代码就会等待,直到 fetch 完成。

1
2
3
4
// fetch request
const colors = fetch('../data/colors.json').then(response => response.json());

export default await colors;

兼容性

p-topAwait_com

趋势

p-topAwait

呈上升趋势。

Temporal

mdn文档说明

一个新的日期/时间 API,具体使用https://tc39.es/proposal-temporal/docs/index.html

1
console.log('Initialization complete', Temporal.Now.instant());

兼容性

p-temporal_com

不兼容

趋势

p-temporal

新 APi,也没呈现出趋势

Array.prototype.findLast()

mdn文档说明

findLast() 方法返回数组中满足提供的测试函数条件的最后一个元素的值。如果没有找到对应元素,则返回 undefined

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const inventory = [
{ name: 'apples', quantity: 2 },
{ name: 'bananas', quantity: 0 },
{ name: 'fish', quantity: 1 },
{ name: 'cherries', quantity: 5 },
];

// return true inventory stock is low
function isNotEnough(item) {
return item.quantity < 2;
}

console.log(inventory.findLast(isNotEnough));
// { name: "fish", quantity: 1 }

兼容性

p-findLast_com

Corejs 的 polyfill 可见https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/es.array.find-last.js

趋势

p-findLast

Error.prototype.cause

mdn文档说明

导致一个错误的数据属性实例表明错误的具体的原始致因。
使用它当捕获和抛出收到一个错误更具体的或有用的错误信息仍然为了获得最初的错误。

1
2
3
4
5
try {
connectToDatabase();
} catch (err) {
throw new Error('Connecting to database failed.', { cause: err });
}

兼容性

p-errorcause_com

趋势

p-errorcause

Object.hasOwn()

mdn文档说明

Object.hasOwn() 用来代替 Object.prototype.hasOwnProperty().

1
2
3
4
5
6
7
8
9
10
11
12
const object1 = {
prop: 'exists',
};

console.log(Object.hasOwn(object1, 'prop'));
// Expected output: true

console.log(Object.hasOwn(object1, 'toString'));
// Expected output: false

console.log(Object.hasOwn(object1, 'undeclaredPropertyValue'));
// Expected output: false

兼容性

p-hasown_com

Corejs 的 polyfill 可见https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/es.object.has-own.js

趋势

p-hasown

Regexp Match Indices

mdn文档说明

1
2
3
4
5
6
7
8
9
const regex1 = new RegExp('foo', 'd');

console.log(regex1.hasIndices);
// Expected output: true

const regex2 = new RegExp('bar');

console.log(regex2.hasIndices);
// Expected output: false
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const str1 = 'foo bar foo';

const regex1 = /foo/dg;

console.log(regex1.hasIndices); // Output: true

console.log(regex1.exec(str1).indices[0]); // Output: Array [0, 3]
console.log(regex1.exec(str1).indices[0]); // Output: Array [8, 11]

const str2 = 'foo bar foo';

const regex2 = /foo/;

console.log(regex2.hasIndices); // Output: false

console.log(regex2.exec(str2).indices); // Output: undefined

兼容性

p-indices_com

趋势

p-indices
å