ES6+新增与修改归纳(2019-2022)

一:ES2019

1.1:Symbol.prototype.description

ChromeEdgeFirefoxSafariOperaOpera Mobile
70+79+63+12.1+57+49+
2018-102020-012018-102019-032018-112018-12

返回Symbol对象的可选描述的字符串

Symbol('desc').description;        // "desc"
Symbol.iterator.description;       // "Symbol.iterator"
Symbol.for('foo').description;     // "foo"
`${Symbol('foo').description}bar`; // "foobar"

1.2:Object.fromEntries

ChromeEdgeFirefoxSafariOperaOpera Mobile
73+79+63+12.1+60+52+
2019-032020-012018-102019-032019-042019-05

把键值对列表转换为一个对象;这个键值对列表可以是ArrayMap或者其它实现了可迭代协议(Iteration protocols)的可迭代对象
Object.entries属于相反操作的方法

let entries = new Map([['foo', 'bar'], ['baz', 42]]);
let newObj = Object.fromEntries(entries);  // { foo: "bar", baz: 42 }
// 与Object.entries互转
let newEntries = Object.entries(newObj);   // [['foo', 'bar'], ['baz', 42]]
newObj = Object.fromEntries(newEntries );  // { foo: "bar", baz: 42 }

1.3:删除空格(trimStart/trimEnd/trimLeft/trimRight)

ChromeEdgeFirefoxSafariOperaOpera Mobile
66+79+61+12+53+47+
2018-042020-012018-062018-092018-052018-07

trimStart()trimLeft():从一个字符串的开头移除空白字符
trimEnd()trimRight():从一个字符串的末端移除空白字符
trimStart()trimEnd()为标准名称,trimLeft()trimRight()为兼容web定义的别名

const greeting = '   Hello world!   ';
greeting.trimStart();   // 'Hello world!   '
greeting.trimLeft();    // 'Hello world!   '
greeting.trimEnd();     // '   Hello world!'
greeting.trimRight();   // '   Hello world!'
greeting.trim();        // 'Hello world!' ES5的语法,移除两端空白字符

1.4:Array.prototype.flat/flatMap

ChromeEdgeFirefoxSafariOperaOpera Mobile
69+79+62+12+56+48+
2018-092020-012018-092018-092018-092018-11

flat([depth]):按可指定的深度(depth)遍历数组,并将所有元素与子数组的元素合并为一个新数组;用于扁平化数组

const arr1 = [0, 1, 2, [3, 4]];
arr1.flat();   // [0, 1, 2, 3, 4]
const arr2 = [0, 1, 2, [[[3, 4]]]];
arr2.flat();   // [0, 1, 2, [[3, 4]]]
arr2.flat(2);  // [0, 1, 2, [3, 4]]
arr2.flat(3);  // [0, 1, 2, 3, 4]
arr2.flat(4);  // [0, 1, 2, 3, 4]

flatMap():使用映射函数映射每个元素(等同于map方法),然后将结果压缩成一个新数组(等同于flat(1)depth始终为1);可用于增删元素,实现map遍历下的filter功能,但是其工作效率较低,因为它会不断创建临时数组,并将数组数据复制到结果数据中

const arr1 = [1, 2, 3, 4];
arr1.map(x => x * 2);                 // [2, 4, 6, 8]
arr1.flatMap(x => [x * 2]);           // [2, 4, 6, 8]
arr1.flatMap(x => [x * 2, x]);        // [2, 1, 4, 2, 6, 3, 8, 4]
arr1.flatMap(x => x > 2 ? [x] : []);  // [3, 4]

二:ES2020

2.1:String.prototype.matchAll

ChromeEdgeFirefoxSafariOperaOpera Mobile
73+79+67+13+60+52+
2019-032020-012019-052019-092019-042019-05

返回一个包含所有匹配正则表达式的结果及分组捕获组的迭代器,该迭代器调用结果耗尽后,需要再次调用方法,获取一个新的迭代器
使用时正则表达式需使用g标志,否则抛出TypeError
其内部做了一个regexp的复制,所以不像execlastIndex在字符串扫描时不会改变

const regexp = RegExp('foo[a-z]*','g');
const str = 'table football, foosball';

// 结果耗尽,迭代器为空
const m1 = str.matchAll(regexp);
Array.from(m1);          // [Array(1), Array(1)]
Array.from(m1);          // []

// 与 exec() 的对比,不用反复取值,直接遍历
let match;
while ((match = regexp.exec(str)) !== null) {
    console.log(match); // ['football', ...] ['foosball', ...]
}
const m2 = str.matchAll(regexp);
for (match of m2) {
    console.log(match); // ['football', ...] ['foosball', ...]
}

// 与 match() 的对比,结果包含分组捕获
const regexp2 = /t(e)(st(\d?))/g;
const str2 = 'test1test2';
str2.match(regexp2); // ['test1', 'test2']
let array = [...str2.matchAll(regexp2)];
array[0];  // ['test1', 'e', 'st1', '1', index: 0, ...]
array[1];  // ['test2', 'e', 'st2', '2', index: 5, ...]

2.2:BigInt

ChromeEdgeFirefoxSafariOperaOpera Mobile
①67+/76+79+①68+/70+14+①54+①48+/54+
19-05/19-072020-0119-07/19-102020-092018-0618-11/19-10
①:toLocaleString 方法不支持参数:locales、options

内置对象,它提供了一种方法来表示大于2^53 - 1的整数,这原本是Javascript中可以用Number表示的最大数字(Number.MAX_SAFE_INTEGER),BigInt可以表示任意大的整数

2.3:Promise.allSettled

ChromeEdgeFirefoxSafariOperaOpera Mobile
76+79+71+13+63+54+
2019-072020-012019-122019-092019-082019-10

返回一个在所有给定的promise都已经fulfilledrejected后的promise,并带有一个对象数组,每个对象表示对应的promise结果
有多个彼此不依赖的异步任务成功完成时,或者您总是想知道每个promise的结果时,通常使用它
Promise.all()更适合彼此相互依赖或者在其中任何一个reject时立即结束

const promise1 = Promise.resolve(3);
const promise2 = new Promise((resolve, reject) => {
    setTimeout(reject, 100, 'foo');
});
const promises = [promise1, promise2];

Promise.allSettled(promises)
    .then((results) => results.forEach(result => console.log(result)));
// { status: "fulfilled", value: 3 }
// { status: "rejected", reason: "foo" }

Promise.all(promises)
    .then((results) => console.log(results))
    .catch(console.error);
// foo

2.4:globalThis

ChromeEdgeFirefoxSafariOperaOpera Mobile
71+79+65+12.1+58+50+
2018-122020-012019-012019-032019-012019-02

提供一个标准的方式来获取不同环境下的全局this对象,确保可以在有无窗口的各种环境下正常工作,不必担心它的运行环境
它既是Web中的windowself或者framesWeb Workers中的self,也是Node.js中的global

globalThis === this &&
globalThis === self &&
globalThis === window;
// true

2.5:可选链操作符(?.)

ChromeEdgeFirefoxSafariOperaOpera Mobile
80+80+74+13.1+67+57+
2020-022020-022020-032020-032020-032020-03

允许读取位于连接对象链深处的属性的值,而不必明确验证链中的每个引用是否有效
引用为空(nullundefined)的情况下不会引起错误,短路返回值为undefined
函数调用时,如果给定的函数不存在,也返回undefined
此操作符仅用于取值,不能用于赋值

const adventurer = {
  name: 'Alice',
  cat: { name: 'Dinah' }
};
const dogName = adventurer.dog?.name; // undefined
adventurer.someNonExistentMethod?.(); // undefined

2.6:空值合并操作符(??)

ChromeEdgeFirefoxSafariOperaOpera Mobile
80+80+72+13.1+67+57+
2020-022020-022020-022020-032020-032020-03

逻辑操作符,当左侧的操作数为空(nullundefined)时,返回其右侧操作数,否则返回左侧操作数
逻辑或(||)的判断条件是“假”(falsy),??的判断条件为“空”(nullishnullundefined

null ?? 'string';  // "string"
null || 'string';  // "string"
0 ?? 42;  // 0
0 || 42;  // 42

三:ES2021

3.1:String.prototype.replaceAll

ChromeEdgeFirefoxSafariOperaOpera Mobile
85+85+77+13.1+71+60+
2020-082020-082020-062020-032020-092020-09

str.replaceAll(regexp|substr, newSubstr|function)
第一个参数可以是一个字符串或一个RegExp(需包含标志g,否则TypeError),用来匹配需替换的字符
第二个参数可以是一个字符串或一个在每次匹配时被调用的函数
效果就是赋予String.prototype.replace全局替换的功能,使用带gRegExp时没有区别

const p = '000222HHHXXX';
p.replaceAll('0', '5');    // '555222HHHXXX'
p.replace('0', '5');       // '500222HHHXXX'
const regex = /h/ig;
p.replaceAll(regex, 'O');  // '000222OOOXXX'
p.replace(regex, 'O');     // '000222OOOXXX'

3.2:Promise.any

ChromeEdgeFirefoxSafariOperaOpera Mobile
85+85+79+14+71+60+
2020-082020-082020-072020-092020-092020-09

3.3:WeakRef

ChromeEdgeFirefoxSafariOperaOpera Mobile
84+85+79+14.1+
2020-072020-082020-072021-04

允许保留对另一个对象的弱引用,且不会阻止被弱引用对象被垃圾回收机制回收
因为各版本的垃圾回收机制可能存在差异,难以保证实现效果能保证一样,所以正确使用WeakRef对象时需要仔细的考虑,最好尽量避免使用
没有特殊需求很难用到的一个东西,具体要用还得去看详细的文档说明

3.4:逻辑赋值(&&=、||=、??=)

ChromeEdgeFirefoxSafariOperaOpera Mobile
85+85+79+14+71+60+
2020-082020-082020-072020-092020-092020-09

&&=左侧变量为truthy时,进行赋值;x &&= y等价于x && (x = y),非等价x = x && y
||=左侧变量为falsy时,进行赋值;x ||= y等价于x || (x = y),非等价x = x || y
??=左侧变量为nullish时,进行赋值;x ??= y等价于x ?? (x = y),非等价x = x ?? y

const a = { duration: 50, title: '', name: '' };

a.duration &&= 10;
console.log(a.duration);  // 10
a.duration ||= 20;
console.log(a.duration);  // 10
a.duration ??= 30;
console.log(a.duration);  // 10

a.title &&= 'S1';
console.log(a.title);     // ''
a.title ||= 'S2';
console.log(a.title);     // 'S2'
a.title ??= 'S3';
console.log(a.title);     // 'S2'

a.null1 &&= 'S1';
console.log(a.null1);     // undefined
a.null2 ??= 'S2';
console.log(a.null2);     // 'S2'
a.null3 ||= 'S3';
console.log(a.null3);     // 'S3'

a.name ??= 'S3';
console.log(a.name);      // ''
a.name ||= 'S2';
console.log(a.name);      // 'S2'

3.5:数字分隔符(_)

ChromeEdgeFirefoxSafariOperaOpera Mobile
75+79+70+13+62+
2019-062020-012019-102019-092019-06

使用下划线 (_, U+005F) 作为分隔符,为方便数字文字的可读性,包括整数和浮点数

// 可用于金额,方便阅读
123_00                  // 123元,12300分/$123,123美分

// 通用写法,千分位分割
12_000                  // 12k
1_234_500               // 1,234,500
1_000_000_000           // 10亿
101_475_938.38          // 亿级单位

// 小数
0.000_001               // 百万分之一
1e10_000                // 10^10_000,10^10000

四:ES2022

4.1:Class:公有类字段(Public class fields)

ChromeEdgeFirefoxSafariOperaOpera Mobile
72+79+69+14.1+60+51+
2019-012020-012019-092021-042019-042019-03

公有字段都是可编辑、可枚举和可配置的属性,且参与原型的继承,即派生类可通过原型链访问字段
字段是在对应的构造函数运行之前添加的,所以在构造函数中可以直接访问字段
字段是通过[[Define]]语义(本质上是Object.defineProperty())添加,其声明并不会触发setter

class ClassField {
    static baseField = 'base field';
    field = 'base';
    // setter
    set fieldSetted(val) {
        console.log('setter:' + val);
    }
    constructor() {
        this.field += ' plus';           // 构造函数中访问字段
        console.log('Base constructor:', this.field);
    }
}
class SubClass extends ClassField {
    field = 'sub';
    // fieldSetted = 'set_1';            // 字段声明不会触发 setter
    constructor() {
        super();
        this.field = this.baseField;     // 实例对象属性值为 undefined
        this.field = SubClass.baseField; // 静态属性原项链访问
        this.fieldSetted = 'set_2';      // this实例赋值,触发 setter
        console.log('Sub constructor:', this.field);
    }
}
new SubClass();
// Base constructor: base plus       super() 中 ClassField 构造
// setter:set_2                     this.fieldSetted 触发 setter
// Sub constructor: base field       通过原型链查找赋值为 'base field'

4.2:Class:私有类字段/方法(Private class fields/methods)

ChromeEdgeFirefoxSafariOperaOpera Mobile
①②74+①②79+90+①②14.1+①②62+①②53+
2019-042019-042021-072021-042019-062019-07
②84+②84+90+15+②70+②60+
2020-072020-072021-072021-092020-072020-09
91+91+90+15+77+64+
2021-052021-052021-072021-092021-062021-05
①:不支持私有方法;②:不支持 in 判断

通过增加哈希前缀#来定义私有类字段/方法,#是名称本身的一部分,声明和访问时也需要加上

  • 私有字段必须先声明,引用未声明的私有字段会抛出语法错误
  • 私有字段仅限在定义它的类的内部访问,派生类/外部调用都会抛出错误
  • 私有字段不能删除,调用delete会抛出语法错误
  • 可使用in运算符检查私有字段是否存在,存在返回true,不存在返回false
  • 私有方法可以是异步、生成器、异步生成器方法,其限制同私有字段一样
class ClassField {
    static #staticField;
    #privateField;

    // 方法中调用私有属性/方法,this只能是当前类,否则报TypeError
    static #baseStaticMethod() {
        this.#staticField = 42;
        return this.#staticField;
    }
    static baseStaticMethod1() {
        return ClassField.#baseStaticMethod.call(ClassField);
    }
    static baseStaticMethod2() {
        return ClassField.#baseStaticMethod.call(this);
    }

    constructor() {
        this.#privateField = 42;     // 直接调用,没问题
        delete this.#privateField;   // delete 操作,语法错误
        this.#undeclaredField = 444; // 未声明字段,语法错误
    }
}
const instance = new ClassField()
instance.#privateField === 42;   // 外部调用,语法错误

class SubClass extends ClassField {};
SubClass.baseStaticMethod1();      // this 指向 ClassField,正常调用
SubClass.baseStaticMethod2();      // this 指向 SubClass,TypeError

4.3:Class:静态初始化块(static initialization blocks)

ChromeEdgeFirefoxSafariOperaOpera Mobile
94+94+93+80+66+
2021-092021-092021-102021-102021-12

使用static{...}的方式在类中添加静态初始化块,同一个类中可以存在多个初始化块,执行顺序即为书写顺序

  • 块内声明变量为局部变量,不会被提升,var定义变量、function函数也不会提升
  • 块内可访问私有属性/字段,包括公有字段和私有字段
  • 块内的this指的是类的构造函数对象,可用于在块内访问静态属性/字段/方法
  • 块内的super可以访问超类的静态属性,包括其属性、字段、方法
  • 块内代码是立即执行的
var y = 'Outer y';
class ClassA {
    static field = 'Inner y';
    static #field = 'Inner y';
    static {
        var y = this.field;
    }
    static {
        console.log(this.field);   // 访问静态属性
        console.log(this.#field);  // 访问私有静态属性
    }
}
class ClassB extends ClassA {
    static {
        console.log(super.field);  // 访问父类静态属性
    }
}
console.log(y); // 块内 var 变量不提升

4.4:取值:.at()(Array、String、TypedArray)

ChromeEdgeFirefoxSafariOperaOpera Mobile
92+92+90+15.4+78+①②65+
2021-072021-072021-072022-032021-082021-10
①:未实现 Array;②:未实现 String

ArrayStringTypedArray(二进制缓冲区),新增at(index)方法,获取指定索引的项目值
index索引为负数时,表示从数组末端开始的相对索引,当索引值超出有效范围,返回结果为undefined

const colors = ['red', 'green', 'blue'];
const sentence = 'The quick brown fox jumps over the lazy dog.';
const int8 = new Int8Array([0, 10, -10, 20, -30, 40, -50]);

colors.at(colors.length - 2);      // 'green'
colors.at(-2);                     // 'green'
sentence.at(sentence.length - 2);  // 'g'
sentence.at(-2);                   // 'g'
int8.at(int8.length - 2);          // 40
int8.at(-2);                       // 40

4.5:Object.hasOwn()

ChromeEdgeFirefoxSafariOperaOpera Mobile
93+93+92+15.4+79+66+
2021-082021-082021-092022-032021-092021-12

hasOwn(instance, prop)用来判断指定对象是否包含指定属性,仅限该对象声明的属性,继承属性不算
正常情况下,使用hasOwnProperty也可以得到效果结果,但hasOwnProperty属于继承属性,当原型链上的该方法不存在、或被修改之后,该方法可能会失效、或者抛出错误

const obj = {};
obj.prop = 'exists';
obj.prop2 = null;
obj.prop3 = undefined;

Object.hasOwn(obj, 'prop');             // true
Object.hasOwn(obj, 'toString');         // false
Object.hasOwn(obj, 'hasOwnProperty');   // false
Object.hasOwn(obj, 'prop2');            // true
Object.hasOwn(obj, 'prop3');            // true
Object.hasOwn(obj, 'prop4');            // false

'prop' in obj;                          // true
'toString' in obj;                      // true
'hasOwnProperty' in obj;                // true
'prop2' in obj;                         // true
'prop3' in obj;                         // true
'prop4' in obj;                         // false

const foo = Object.create(null);
foo.prop = 'exists';
Object.hasOwn(foo, 'prop');             // true
'prop' in foo;                          // true
foo.hasOwnProperty('prop');             // TypeError

4.6:Error.prototype.cause

ChromeEdgeFirefoxSafariOperaOpera Mobile
93+93+91+15+
2021-082021-082021-082021-09

该属性用来描述引起该错误的特定原始原因,其值是在options.cause参数中传递给Error()构造函数的值
主要用来封装机器语言的报错,将原因修改为通俗语言表达,用于参考差错

function makeRSA(p, q) {
    if (!Number.isInteger(p) || !Number.isInteger(q)) {
        throw new Error('生成 RSA 密钥需要输入整数.', {
            cause: { code: '非整数', value: [p, q] },
        });
    }
    if (!areCoprime(p, q)) {
        throw new Error('生成 RSA 密钥需要输入两个互质整数.', {
            cause: { code: '非质数', values: [p, q] },
        })
    }
    // …
}

4.7:正则修饰符:d

ChromeEdgeFirefoxSafariOperaOpera Mobile
90+90+88+15+76+64+
2021-042021-042021-042021-092021-042021-05

使用该修饰符,会在结果中添加一个属性:indices
该属性的值包含所有匹配项的索引信息,其索引值是相对的原始字符串的索引
未匹配数据时,将返回undefined

const re1 = /a+(?<Z>z)?/d;
const s1 = "xaaaz";
const m1 = re1.exec(s1);
console.log(m1.indices);
/*[
    'aaaz', 
    'z', 
    index: 1, 
    input: 'xaaaz', 
    groups: {
        Z: "z"
    }, 
    indices: [
        [1, 5], 
        [4, 5], 
        groups: {
            Z: [4, 5]
        }
    ]
]*/
// 没有匹配时,返回 `undefined`:
const m2 = re1.exec("xaaay");
m2.indices[1] === undefined;
m2.indices.groups["Z"] === undefined;