ECMAScript 2022(ES13)于6月22日发布,为JavaScript编纂了最新的一批新功能。每一个技术规范都是与现实世界的使用情况不断共舞的里程碑。在开发者使用JavaScript的过程中,我们不断地发现机会,并将语言推向新的领域。ECMAScript规范通过正式确定新的功能来回应。这些反过来又为JavaScript的持续发展建立了一个新的基线。
ES13规范为JavaScript带来了八个新特性。让我们开始学习这些你今天就可以使用的新特性。
类字段
类字段是一个总的建议,包含了对处理JavaScript类的成员的几个改进。类的公共和私有实例字段,私有实例方法和访问器,以及静态类特性。
公共和私有实例字段
以前,在class 关键字内声明成员字段时,标准的做法是在构造函数中引入。最新的ECMAScript规范让我们将成员字段内联定义为类主体的一部分。 如清单1所示,我们可以使用一个标签来表示一个私有字段。
清单1.内联公共和私有类字段
class Song {
title = "";
#artist = "";
constructor(title, artist){
this.title = title;
this.#artist = artist;
}
}
let song1 = new Song("Only a Song", "Van Morrison");
console.log(song1.title);
// outputs “Only a Song”
console.log(song1.artist);
// outputs undefined
在清单1中,我们使用class 关键字定义了一个类,Song 。这个类有两个成员,title 和artist 。artist 成员的前缀是哈希(#)符号,所以它是私有的。我们允许在构造函数中设置这些字段。注意,构造函数必须再次访问带有哈希前缀的this.#artist ;否则,它将用一个公共成员覆盖该字段。
接下来,我们定义一个Song 类的实例,通过构造函数设置这两个字段。 然后,我们将字段输出到控制台。重点是,song1.artist 对外界不可见,并且输出未定义。
还要注意的是,即使是song1.hasOwnProperty("artist") ,也会返回false。此外,我们以后不能用赋值在类上创建私有字段。
总的来说,这是一个很好的补充,使得代码更加简洁。 大多数浏览器已经支持公共和私有实例字段有一段时间了,很高兴看到它们被正式纳入。
私有实例方法和访问器
哈希符号也可以作为方法和访问器的前缀。 对可见性的影响与对私有实例字段的影响完全相同。 因此,你可以为Song.artist 字段添加一个私有的setter和一个公共的getter,如清单2所示。
清单2.私有实例方法和访问器
class Song {
title = "";
#artist = "";
constructor(title, artist){
this.title = title;
this.#artist = artist;
}
get getArtist() {
return this.#artist;
}
set #setArtist(artist) {
this.#artist = artist;
}
}
静态成员
类字段的建议也引入了静态成员。这些成员的外观和工作方式与Java中的类似:如果一个成员有static 关键字修饰,它就存在于类中,而不是对象实例。 你可以在Song 类中添加一个静态成员,如清单3所示。
清单3.在一个类中添加一个静态成员
class Song {
static label = "Exile";
}
然后,该字段只能通过类名Song.label 。与Java不同,JavaScript实例不持有对共享静态变量的引用。注意,可以用静态#label ;也就是说,可以有一个私有的静态字段。
正则匹配索引
Regexmatch 已经升级,包括了更多关于匹配组的信息。出于性能的考虑,只有在正则表达式中加入/d 标志时,才会包括这些信息。(关于/d regex的含义,请参见ECMAScript提案中的正则匹配索引的深入研究)。
基本上,使用/d 标志会使重码引擎包括所有匹配子字符串的开始和结束。当该标志出现时,indices 属性在exec 结果中会包含一个二维数组,其中第一维代表匹配,第二维代表匹配的开始和结束。
在命名组的情况下,索引将有一个名为groups 的成员,其第一维包含组的名称。请看清单4,它取自提案。
清单4.Regex组索引
const re1 = /a+(?<Z>z)?/d;
// block 1
const s1 = "xaaaz";
const m1 = re1.exec(s1);
m1.indices[0][0] === 1;
m1.indices[0][1] === 5;
s1.slice(...m1.indices[0]) === "aaaz";
// block 2
m1.indices[1][0] === 4;
m1.indices[1][1] === 5;
s1.slice(...m1.indices[1]) === "z";
// block 3
m1.indices.groups["Z"][0] === 4;
m1.indices.groups["Z"][1] === 5;
s1.slice(...m1.indices.groups["Z"]) === "z";
// block 4
const m2 = re1.exec("xaaay");
m2.indices[1] === undefined;
m2.indices.groups["Z"] === undefined;
在清单4中,我们创建了一个正则表达式,与a 字符匹配一次或多次,然后是一个命名的匹配组(命名为Z ),与z 字符匹配0次或多次。
代码块1展示了m1.indices[0][0] 和m1.indices[0][1] 分别包含1和5。这是因为正则表达式的第一次匹配是从第一个a 到以z 结尾的字符串。代码块2展示了对z 字符的同样情况。
块3显示了通过m1.indices.groups 访问带有命名组的第一个维度。有一个匹配的组,即最后的z 字符,它的起点是4,终点是5。
最后,第4块展示了不匹配的索引和组将返回未定义。
底线是,如果你需要访问组在字符串中匹配的细节,你现在可以使用regex匹配索引的功能来获得它。
顶层等待
ECMAScript规范现在包括了打包异步模块的能力。 当你导入一个用await 包装的模块时,包括的模块将不会执行,直到所有的await。这避免了在处理相互依赖的异步模块调用时潜在的竞赛条件。 详情请看顶层await建议。
清单5包括一个从提案中借用的例子。
清单5.顶层 await
// awaiting.mjs
import { process } from "./some-module.mjs";
const dynamic = import(computedModuleSpecifier);
const data = fetch(url);
export const output = process((await dynamic).default, await data);
// usage.mjs
import { output } from "./awaiting.mjs";
export function outputPlusValue(value) { return output + value }
console.log(outputPlusValue(100));
setTimeout(() => console.log(outputPlusValue(100), 1000);
注意在awaiting.mjs 中,在其使用的依赖模块dynamic 和data 前面的await 关键字。这意味着当usage.mjs 导入awaiting.mjs 时,usage.mjs 将不会被执行,直到awaiting.mjs 依赖模块加载完毕。
对私有字段进行符合人体工程学的品牌检查
作为开发者,我们希望代码是舒适的--也就是符合人体工程学的私有字段。 这个新特性让我们可以检查一个类上是否存在一个私有字段,而不需要诉诸于异常处理。
清单6显示了这种新的、符合人体工程学的方法,即使用in 关键字从一个类中检查一个私有字段。
清单6.检查一个私有字段的存在
class Song {
#artist;
checkField(){
return #artist in this;
}
}
let foo = new Song();
foo.checkField(); // true
清单6是设计好的,但其思想是明确的。当你发现自己需要检查一个类中的私有字段时,你可以使用这样的格式:#fieldName in object 。
用.at()进行负向索引
arr[arr.length -2] 的日子已经一去不复返了。内置索引器上的.at方法现在支持负数索引,如清单7所示。
清单7.使用.at()的负数索引
let foo = [1,2,3,4,5];
foo.at(3); // == 3
hasOwn
hasOwn
Object.hasOwn是Object.hasOwnProperty 的一个改进版本。它适用于一些边缘情况,比如用Object.create(null) 创建一个对象时。注意,hasOwn 是一个静态方法--它不存在于实例上。
清单8.hasOwn()的作用
let foo = Object.create(null);
foo.hasOwnProperty = function(){};
Object.hasOwnProperty(foo, 'hasOwnProperty'); // Error: Cannot convert object to primitive value
Object.hasOwn(foo, 'hasOwnProperty'); // true
清单8显示,你可以在用Object.create(null) 创建的foo 实例上使用Object.hasOwn 。
类静态块
这里有一个机会,Java开发者可以说,哦,我们从90年代起就有这个东西了。 ES 2022将静态初始化块引入了JavaScript。本质上,你可以在类加载时运行的代码块上使用static 关键字,它就可以访问静态成员。
清单9有一个简单的例子,使用静态块来初始化一个静态值。
清单9.静态块
class Foo {
static bar;
static {
this.bar = “test”;
}
}
错误原因
最后但并非最不重要的是,Error 类现在加入了原因支持。这允许在错误链中进行类似Java的堆栈跟踪。错误构造函数现在允许一个包括cause 字段的选项对象,如清单10所示。
清单10.错误原因
throw new Error('Error message', { cause: errorCause });