ES2019没带来大量新语法,主要都是一些新API。而其中一些新特性没能赶上ES2019的正式发布,但实际上浏览器都已经实现了、已经进入候选发布阶段,估计会在ES2020中正式发布,本文将会介绍这些浏览器已经实现的新特性,因为它们估计已经与正式版一致。
* ES2020有可能又会导入大量新特性,例如协议/接口、装饰器、管道、JSX、模式匹配等等,我担心它会不会像ES4那样流产……
新语法
可选异常catch捕获
旧语法要求catch
必须捕获那个异常,而新语法则放松这个要求,捕获异常变成可选的了。
'use strict';
try {
throw new Error();
} catch { // 没有捕获
alert('Error here!');
}
function *g() {
try {
yield 1;
throw new Error();
} catch { // 没有捕获
alert('Error here!');
return 2;
}
}
const itr = g();
alert(itr.next().value === 1 && itr.next().value === 2);
(async () => {
try {
await Promise.reject(new Error());
} catch { // 没有捕获
alert('Error here!');
}
})();
数字字面量可以使用下划线
这个特性已经使用一段时间了,因为用下划线分隔一下数字的确比较容易阅读。不过要注意,Number
或parseInt/Float
不可以使用下划线分隔数字。
'use strict';
alert(123_456); // 一般的十进制整数可以用
alert(123_456.789_100); // 一般的十进制小数也可以用
alert(0x12_34_56_78); // 十六进制整数当然也可以用
alert(0o1234_5670); // 八进制整数当然也可以用
alert(0b10110010_10010110); // 二进制整数当然也可以用
alert(isNaN(Number('123_456')));
alert(parseInt('123_456') === 123);
大整型(四阶草案)
看到ECMA委员会竟然把大整数当作内置对象/语法我是很惊讶的,因为本站所编写的欧拉计划不时要从第三方导入BigInt
库,一直没想过JS内置就有……逻辑上它已经支持任意大小的整数,且支持除无符号右移>>>
以外所有原生的数字操作符号。(因为所有大整数都是有符号数)
'use strict';
const x = 1n, // 只需要在一般整数最后带上“n”尾缀就可以
y = 2n,
z = x + y, // 操作完之后依然是bigint
a = x - y,
b = BigInt(Number.MAX_SAFE_INTEGER) + 1n, // 使用构造函数可传入一般的number
c = BigInt('0xFFFFFFFF'), // 十六/八/二进制字符串也可以
d = x / y; // 0n,就像Java的整型那样,小数将会被忽略
alert(`z: ${z} (${typeof z})
a: ${a} (${typeof a})
-a: ${-a} (${typeof -a})
b: ${b}
c: ${c}
d: ${d}
0n == 0 => ${0n == 0}
0n === 0 => ${0n === 0}
2n ** 5n => ${2n ** 5n}`); // 大整型只可以与大整型互相操作
try {
let k = 2n ** 5;
} catch (e) {
alert(e);
}
try {
+a; // 这个操作符只能够用在一般的number
} catch (e) {
alert(e);
}
if (1n < 2) // 但比较符号可以混合操作
alert(true);
const e = 0b10110101n,
f = 0b11010011n; // 十六/八进制数字也可以后面带上“n”
alert(`e & f => 0b${(e & f).toString(2)}
e | f => 0b${(e | f).toString(2)}`); // 位操作不能与一般number混合操作
const INT64_MAX_VALUE = 2n ** 63n - 1n;
alert(BigInt.asIntN(64, INT64_MAX_VALUE));
alert(BigInt.asIntN(64, INT64_MAX_VALUE + 1n)); // 有符号数溢出后就变成最小值
const UINT32_MAX_VALUE = 2n ** 32n - 1n;
alert(BigInt.asUintN(32, UINT32_MAX_VALUE));
alert(BigInt.asUintN(32, UINT32_MAX_VALUE + 1n)); // 无符号数溢出后变成0
类的私有成员(三阶草案)
有这个新特性我也挺惊喜的,但是我并不喜欢用井号#
来标注类的私有成员,因为这个符号在很多编程语言当中都被视为代码注释……不过,这个方案总比使用符号来模拟私有成员好得多:
'use strict';
class C {
/*
constructor() {
this.#z = 'zzz'; // 不可以在构造函数声明似有属性
}
*/
#x = 'private prop'; // 实例私有属性
/*
#f() {} // 实例私有方法尚未支持
*/
g() {
alert('public method\n' + this.#x);
}
static #y = 'static private prop'; // 静态私有属性
/*
static #f() {} // 静态私有方法尚未支持
*/
static g() {
alert('static public method\n' + this.#y);
}
}
class D extends C {
f() {
// 这种写法当然不能获取父类的私有属性
/* alert(this.#x); */
// 这甚至被视为语法错误,因为D没有自己的私有#x属性
}
}
alert(C['#y']);
C.g();
const c = new C();
alert(c['#x']);
c.g();
// 继承回来的方法当然照常运作
(new D()).g();
新API
Object.fromEntries()静态方法
任何支持entries()
实例方法的对象都可以作为Object.fromEntries()
方法的参数生成新对象:
'use strict';
const obj = { a: 1, b: 2, c: 3 },
ary = Object.entries(obj), // [['a', 1], ['b', 2], ['c', 3]]
map = new Map(ary),
r1 = Object.fromEntries(ary),
r2 = Object.fromEntries(map);
alert(JSON.stringify(r1) === JSON.stringify(r2) &&
r1 !== obj && r2 !== obj && r1 !== r2);
字符串的新API
增加了的都是些便利方法,没什么新概念。
trimStart()与trimEnd()方法
这些方法都是trim()
的变种:
'use strict';
const s = ' \nabc \n';
alert('|' + s.trimStart() + '|');
alert('|' + s.trimLeft() + '|');
alert(s.trimStart === s.trimLeft);
alert('|' + s.trimEnd() + '|');
alert('|' + s.trimRight() + '|');
alert(s.trimEnd === s.trimRight);
matchAll()方法(提案)
旧的字符串API的match()
方法,如果正则表达式使用了全局g
模式,只会返回匹配了的最大字符串,若我们需要获取正则表达式的每次匹配的细节,那么就只可以在迭代中使用正则表达式的exec()
方法。而matchAll()
方法就是返回一个包含每次匹配细节的迭代器:
'use strict';
alert(JSON.stringify('test1test2'.match(/t(e)(st(\d?))/g)));
alert(JSON.stringify([...'test1test2'.matchAll(/t(e)(st(\d?))/g)]));
数组的flat()与flatMap()方法
ES2019总算引入了函数式编程中的自动平面化API。
'use strict';
const a = [1, [2, 3], [4, [5, 6]]];
alert(JSON.stringify(a.flat())); // 默认平面化只能自动下探1层
alert(JSON.stringify(a.flat(2))); // 下探2层
const b = [1, 2, 3, 4, 5];
alert(JSON.stringify(b.flatMap(x => [x ** 2]))); // 可自动解开1层
alert(JSON.stringify(b.map(x => [x ** 2])));
符号增加description属性
添加这个属性之后符号总算不像一个黑洞了……原本的符号系统需要靠String(symbol)
这样强制类型转换才知道目标符号叫什么,现在可以直接获取了:
'use strict';
alert(Symbol('xxx').description);
alert(Symbol('').description === '');
alert(Symbol().description === undefined); // 没有描述的情况
Promise.allSettled()静态方法(四阶草案)
我以为这个方法是ES2015就导入了,原来在ES2019也只不过是四阶草案……它可以用于同时并发执行多个任务,所有任务都结束才算结束,不管哪个有错:
'use strict';
async function task() {
if (Math.random() > 0.5)
return 'OK';
else
throw 'FAIL';
}
Promise.allSettled([task(), task(), task()])
.then(results => { // 只有then回调,即便其中有任务报错
alert(results.map(r => JSON.stringify(r)).join('\n'));
});
动态import导入函数(三阶草案)
ES2015虽然已经引入了import/export
模块系统,但是有一个关于import
的提案被拖延到现在都还没正式发布——那就是可以视import
为一个全局函数,而它会返回一个Promise
,这样就可以在JS文件的范围内也可以使用脚本异步加载了(原本必须在HTML的<script />
标签声明async
才可以异步加载)。
'use strict';
const mod1Url = window.URL.createObjectURL(new Blob([`
function cube(x) {
return x * x * x;
}
const foo = Math.PI + Math.SQRT2;
export { cube, foo as FOO };
`], { type: 'application/javascript' })); // 假装是一个独立的JS文件
import(mod1Url) // 可以使用Promise API获取模块
.then(mod1 => alert(`cube(3) => ${mod1.cube(3)}`))
.finally(async () => {
const mod1 = await import(mod1Url); // 当然,也可以await
alert(`FOO => ${mod1.FOO}`);
});
64位整型数组与数据视图(提案)
……64位长整型被ECMA委员会视为BigInt
……很无语,但是的确,64位长整型在JS只能在使用BigInt
的情况下才可以获取得到。所以带类型数组%Typed%Array
便增加了BigInt64Array
和BigUint64Array
,而数据试图DataView
也增加了set/getInt64()
和set/getUint64()
。
'use strict';
const buff = new ArrayBuffer(8),
a = new BigInt64Array(buff),
b = new BigUint64Array(buff);
a[0] = 0xFFFF_FFFF_FFFF_FFFFn;
b[0] = 0xFFFF_FFFF_FFFF_FFFFn;
alert(a[0] === -1n);
alert(b[0]);
a[0] = 0x7FFF_FFFF_FFFF_FFFFn;
alert('0x' + (a[0]).toString(16));
const dv = new DataView(buff);
dv.setBigUint64(0, 0x8234_5678_9ABC_DEF0n);
alert(dv.getBigInt64(0));
alert(dv.getBigUint64(0));
globalThis统一全局对像(提案)
由于在不同的执行环境当中,JS的全局对象的变量名都不一样,例如在浏览器页面中它叫window
、在Web Worker环境中它叫self
、在Node.JS中由叫做global
,而在松散模式的函数中还可以直接使用this
(不过在严格模式中它会变为undefined
)。有鉴于此ECMA委员会统一了这个全局对象的名称为globalThis
,原有的全局对象变量名依然能够使用。
'use strict';
alert(window === globalThis);
const workerUrl = window.URL.createObjectURL(new Blob([`
onmessage = function (e) {
postMessage(self === globalThis);
};
`], { type: 'application/javascript' }));
const worker = new Worker(workerUrl);
worker.onmessage = function (e) {
alert(e.data);
};
worker.postMessage('Hi!');
打赏作者