ES6+新增与修改归纳(2019-2022)
一:ES2019
1.1:Symbol.prototype.description
Chrome | Edge | Firefox | Safari | Opera | Opera Mobile |
70+ | 79+ | 63+ | 12.1+ | 57+ | 49+ |
2018-10 | 2020-01 | 2018-10 | 2019-03 | 2018-11 | 2018-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
Chrome | Edge | Firefox | Safari | Opera | Opera Mobile |
73+ | 79+ | 63+ | 12.1+ | 60+ | 52+ |
2019-03 | 2020-01 | 2018-10 | 2019-03 | 2019-04 | 2019-05 |
把键值对列表转换为一个对象;这个键值对列表可以是Array
、Map
或者其它实现了可迭代协议(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)
Chrome | Edge | Firefox | Safari | Opera | Opera Mobile |
66+ | 79+ | 61+ | 12+ | 53+ | 47+ |
2018-04 | 2020-01 | 2018-06 | 2018-09 | 2018-05 | 2018-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的语法,移除两端空白字符
- String.prototype.trimStart() – JavaScript | MDN (mozilla.org)
- String.prototype.trimRight() – JavaScript | MDN (mozilla.org)
1.4:Array.prototype.flat/flatMap
Chrome | Edge | Firefox | Safari | Opera | Opera Mobile |
69+ | 79+ | 62+ | 12+ | 56+ | 48+ |
2018-09 | 2020-01 | 2018-09 | 2018-09 | 2018-09 | 2018-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]
- Array.prototype.flat() – JavaScript | MDN (mozilla.org)
- Array.prototype.flatMap() – JavaScript | MDN (mozilla.org)
二:ES2020
2.1:String.prototype.matchAll
Chrome | Edge | Firefox | Safari | Opera | Opera Mobile |
73+ | 79+ | 67+ | 13+ | 60+ | 52+ |
2019-03 | 2020-01 | 2019-05 | 2019-09 | 2019-04 | 2019-05 |
返回一个包含所有匹配正则表达式的结果及分组捕获组的迭代器,该迭代器调用结果耗尽后,需要再次调用方法,获取一个新的迭代器
使用时正则表达式需使用g
标志,否则抛出TypeError
其内部做了一个regexp
的复制,所以不像exec
,lastIndex
在字符串扫描时不会改变
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
Chrome | Edge | Firefox | Safari | Opera | Opera Mobile |
①67+/76+ | 79+ | ①68+/70+ | 14+ | ①54+ | ①48+/54+ |
19-05/19-07 | 2020-01 | 19-07/19-10 | 2020-09 | 2018-06 | 18-11/19-10 |
内置对象,它提供了一种方法来表示大于2^53 - 1
的整数,这原本是Javascript
中可以用Number
表示的最大数字(Number.MAX_SAFE_INTEGER
),BigInt
可以表示任意大的整数
2.3:Promise.allSettled
Chrome | Edge | Firefox | Safari | Opera | Opera Mobile |
76+ | 79+ | 71+ | 13+ | 63+ | 54+ |
2019-07 | 2020-01 | 2019-12 | 2019-09 | 2019-08 | 2019-10 |
返回一个在所有给定的promise
都已经fulfilled
或rejected
后的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
Chrome | Edge | Firefox | Safari | Opera | Opera Mobile |
71+ | 79+ | 65+ | 12.1+ | 58+ | 50+ |
2018-12 | 2020-01 | 2019-01 | 2019-03 | 2019-01 | 2019-02 |
提供一个标准的方式来获取不同环境下的全局this
对象,确保可以在有无窗口的各种环境下正常工作,不必担心它的运行环境
它既是Web
中的window
、self
或者frames
,Web Workers
中的self
,也是Node.js
中的global
globalThis === this && globalThis === self && globalThis === window; // true
2.5:可选链操作符(?.)
Chrome | Edge | Firefox | Safari | Opera | Opera Mobile |
80+ | 80+ | 74+ | 13.1+ | 67+ | 57+ |
2020-02 | 2020-02 | 2020-03 | 2020-03 | 2020-03 | 2020-03 |
允许读取位于连接对象链深处的属性的值,而不必明确验证链中的每个引用是否有效
引用为空(null
或undefined
)的情况下不会引起错误,短路返回值为undefined
函数调用时,如果给定的函数不存在,也返回undefined
此操作符仅用于取值,不能用于赋值
const adventurer = { name: 'Alice', cat: { name: 'Dinah' } }; const dogName = adventurer.dog?.name; // undefined adventurer.someNonExistentMethod?.(); // undefined
2.6:空值合并操作符(??)
Chrome | Edge | Firefox | Safari | Opera | Opera Mobile |
80+ | 80+ | 72+ | 13.1+ | 67+ | 57+ |
2020-02 | 2020-02 | 2020-02 | 2020-03 | 2020-03 | 2020-03 |
逻辑操作符,当左侧的操作数为空(null
或undefined
)时,返回其右侧操作数,否则返回左侧操作数
逻辑或(||)的判断条件是“假”(falsy
),??
的判断条件为“空”(nullish
:null
或undefined
)
null ?? 'string'; // "string" null || 'string'; // "string" 0 ?? 42; // 0 0 || 42; // 42
三:ES2021
3.1:String.prototype.replaceAll
Chrome | Edge | Firefox | Safari | Opera | Opera Mobile |
85+ | 85+ | 77+ | 13.1+ | 71+ | 60+ |
2020-08 | 2020-08 | 2020-06 | 2020-03 | 2020-09 | 2020-09 |
str.replaceAll(regexp|substr, newSubstr|function)
第一个参数可以是一个字符串或一个RegExp
(需包含标志g
,否则TypeError
),用来匹配需替换的字符
第二个参数可以是一个字符串或一个在每次匹配时被调用的函数
效果就是赋予String.prototype.replace
全局替换的功能,使用带g
的RegExp
时没有区别
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
Chrome | Edge | Firefox | Safari | Opera | Opera Mobile |
85+ | 85+ | 79+ | 14+ | 71+ | 60+ |
2020-08 | 2020-08 | 2020-07 | 2020-09 | 2020-09 | 2020-09 |
3.3:WeakRef
Chrome | Edge | Firefox | Safari | Opera | Opera Mobile |
84+ | 85+ | 79+ | 14.1+ | ❌ | ❌ |
2020-07 | 2020-08 | 2020-07 | 2021-04 | ➖ | ➖ |
允许保留对另一个对象的弱引用,且不会阻止被弱引用对象被垃圾回收机制回收
因为各版本的垃圾回收机制可能存在差异,难以保证实现效果能保证一样,所以正确使用WeakRef
对象时需要仔细的考虑,最好尽量避免使用
没有特殊需求很难用到的一个东西,具体要用还得去看详细的文档说明
3.4:逻辑赋值(&&=、||=、??=)
Chrome | Edge | Firefox | Safari | Opera | Opera Mobile |
85+ | 85+ | 79+ | 14+ | 71+ | 60+ |
2020-08 | 2020-08 | 2020-07 | 2020-09 | 2020-09 | 2020-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'
- Logical AND assignment (&&=) – JavaScript | MDN (mozilla.org)
- Logical OR assignment (||=) – JavaScript | MDN (mozilla.org)
- 逻辑空赋值 (??=) – JavaScript | MDN (mozilla.org)
3.5:数字分隔符(_)
Chrome | Edge | Firefox | Safari | Opera | Opera Mobile |
75+ | 79+ | 70+ | 13+ | 62+ | ❌ |
2019-06 | 2020-01 | 2019-10 | 2019-09 | 2019-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)
Chrome | Edge | Firefox | Safari | Opera | Opera Mobile |
72+ | 79+ | 69+ | 14.1+ | 60+ | 51+ |
2019-01 | 2020-01 | 2019-09 | 2021-04 | 2019-04 | 2019-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)
Chrome | Edge | Firefox | Safari | Opera | Opera Mobile |
①②74+ | ①②79+ | 90+ | ①②14.1+ | ①②62+ | ①②53+ |
2019-04 | 2019-04 | 2021-07 | 2021-04 | 2019-06 | 2019-07 |
②84+ | ②84+ | 90+ | 15+ | ②70+ | ②60+ |
2020-07 | 2020-07 | 2021-07 | 2021-09 | 2020-07 | 2020-09 |
91+ | 91+ | 90+ | 15+ | 77+ | 64+ |
2021-05 | 2021-05 | 2021-07 | 2021-09 | 2021-06 | 2021-05 |
通过增加哈希前缀#
来定义私有类字段/方法,#
是名称本身的一部分,声明和访问时也需要加上
- 私有字段必须先声明,引用未声明的私有字段会抛出语法错误
- 私有字段仅限在定义它的类的内部访问,派生类/外部调用都会抛出错误
- 私有字段不能删除,调用
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)
Chrome | Edge | Firefox | Safari | Opera | Opera Mobile |
94+ | 94+ | 93+ | ❌ | 80+ | 66+ |
2021-09 | 2021-09 | 2021-10 | ➖ | 2021-10 | 2021-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)
Chrome | Edge | Firefox | Safari | Opera | Opera Mobile |
92+ | 92+ | 90+ | 15.4+ | 78+ | ①②65+ |
2021-07 | 2021-07 | 2021-07 | 2022-03 | 2021-08 | 2021-10 |
Array
、String
、TypedArray
(二进制缓冲区),新增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
- Array.prototype.at() – JavaScript | MDN (mozilla.org)
- String.prototype.at() – JavaScript | MDN (mozilla.org)
- TypedArray.prototype.at() – JavaScript | MDN (mozilla.org)
4.5:Object.hasOwn()
Chrome | Edge | Firefox | Safari | Opera | Opera Mobile |
93+ | 93+ | 92+ | 15.4+ | 79+ | 66+ |
2021-08 | 2021-08 | 2021-09 | 2022-03 | 2021-09 | 2021-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
Chrome | Edge | Firefox | Safari | Opera | Opera Mobile |
93+ | 93+ | 91+ | 15+ | ❌ | ❌ |
2021-08 | 2021-08 | 2021-08 | 2021-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
Chrome | Edge | Firefox | Safari | Opera | Opera Mobile |
90+ | 90+ | 88+ | 15+ | 76+ | 64+ |
2021-04 | 2021-04 | 2021-04 | 2021-09 | 2021-04 | 2021-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;