2019 谷歌 I/O 大会上提出的 JavaScript 新特性,你错过了吗?

阅读数:2266 2019 年 8 月 6 日

本文总结了 2019 年谷歌 I/O 大会上 Mathias 和 Sathya 提出来的 JavaScript 新规范功能。

本文从对线程认识的简要刷新、事件循环、关于事件循环的常见问题和错误认识三个不同层面进行说明,进一步挖掘了 Node 的核心。

正则表达式 lookbehind

正则表达式(RegEx正则表达式)在任何语言里都是强大的功能。在字符串中搜索复杂的模式时正则表达式就能大显身手了。之前 JavaScript 中的正则表达式实现已经非常齐全了,唯一缺少的就是lookbehind

Lookahead

首先我们来了解一下正则表达式中的lookahead是什么含义,JavaScript 已经支持 lookahead 了。正则表达式中的 lookahead 语法允许你在字符串中选出的模式具有以下属性:另一个已知模式正好紧靠这个模式或不与其相邻,或者在这个模式之后。例如在字符串“MangoJuice,VanillaShake,GrapeJuice”中,我们可以使用正向 lookahead 语法来查找旁边有J​​uice的单词,即MangoGrape

有两种类型的 lookahead,分别是正向 lookahead 和负向或否定的 lookahead。

正向 lookahead

正向 lookahead 选出的模式具有以下属性:另一个已知模式位于选出模式之后。正向 lookahead 的语法如下。

复制代码
/[a-zA-Z]+(?=Juice)/

上面的模式选出了大写或小写字母的单词,单词旁边都会有Juice。不要把它和正则表达式中的捕获组混淆。Lookahead 和 Lookbehind 都是写在括号里的,但它们没有被捕获。下面看一个正向 lookahead 的实际例子。

复制代码
const testString = "MangoJuice, VanillaShake, GrapeJuice";
const testRegExp = /[a-zA-Z]+(?=Juice)/g;
const matches = testString.match( testRegExp
);
console.log( matches ); // ["Mango", "Grape"]

负向 lookahead

类似地,上面的例子中如果使用负向 lookahead,就是选出所有后面没有Juice的单词。负向 lookahead 的语法与正向 lookahead 的语法类似,但有一处不同。我们需要把 = 符号换成! 符号。

复制代码
/[a-zA-Z]+(?!Juice)/

上面的正向表达式模式会选出所有后面不跟着Juice的单词。但上面的模式会选出给定字符串中的所有单词,因为给定字符串中的所有单词都不以 Juice 结尾,因此我们需要更具体些的规则。

复制代码
/(Mango|Vanilla|Grape)(?!Juice)/

这种模式将选出MangoVanillaGrape几个单词,它们后面没有跟着 Juice。来看具体的代码。

复制代码
const testString = "MangoJuice, VanillaShake, GrapeJuice";
const testRegExp= /(Mango|Vanilla|Grape)(?!Juice)/g;
const matches = testString.match( testRegExp
);
console.log( matches ); // ["Vanilla"]

Lookbehind

与 lookahead 类似,Lookbehind 也是正则表达式中的一种语法,用它选出的模式具有以下属性:字符串中的某个已知模式位于或不在它的前面。例如,在字符串“FrozenBananas,DriedApples,FrozenFish”中,我们可以使用正向 lookbehind 来找到前面有Frozen的单词,比比如BananasFish

与 lookahead 类似,这里也有一个正向 lookbehind 和负向或否定 lookbehind。

正向 lookbehind

正向 lookbehind 选出的模式具有以下属性:另一个已知模式位于它的前面。正向 lookbehind 的语法如下。

复制代码
/(?<=Frozen)[a-zA-Z]+/

lookbehind 的模式类似于 lookahead,但带有额外的 < 符号,表示在前。上面的模式会选出所有以Frozen开头的单词或者在前面有Frozen的单词。来看具体的操作。

复制代码
const testString = "FrozenBananas, DriedApples, FrozenFish";
const testRegExp = /(?<=Frozen)[a-zA-Z]+/g;
const matches = testString.match( testRegExp
);
console.log( matches ); // ["Bananas", "Fish"]

负向 lookbehind

负向 lookbehind 选出的模式具有以下属性:另一个已知模式不在它的前面。例如要选出“FrozenBananas,DriedApples,FrozenFish”字符串中前面没有 Frozen 的单词,我们将使用以下语法。

复制代码
/(?<!Frozen)[a-zA-Z]+/

但上面的模式将选出字符串中的所有单词,因为所有单词前面都没有 Frozen(FrozenBannanas 这样的单词这里会被视为一整个单词),我们需要更具体一些。

复制代码
/(?<!Frozen)(Bananas|Apples|Fish)/

写在代码中:

复制代码
const testString = "FrozenBananas, DriedApples, FrozenFish";
const testRegExp = /(?<!Frozen)(Bananas|Apples|Fish)/g;
const matches = testString.match( testRegExp
);
console.log( matches ); // ["Apples"]

支持范围——TC39:阶段 4;Chrome:62+;Node:8.10.0+

类字段

类字段(class field)是一种新的语法,用来从类构造函数外部定义实例(对象)的属性。有两种类型的类字段,公共类字段私有类字段

公共类字段

之前我们必须在类构造函数中定义对象上的属性。这些属性是公共的,意味着可以在类(对象)的实例上访问它们。

复制代码
class Dog {
constructor() {
this.name = 'Tommy';
}
}

每当我们有一个扩展父类的类时,必须先从构造函数中调用 super,然后才能在子类上添加属性,如下所述。

复制代码
class Animal {}
class Dog extends Animal {
constructor() {
super(); // call super before using `this` in constructor
this.sound = 'Woof! Woof!';
}
makeSound() {
console.log( this.sound );
}
}
// create instance
const tommy = new Dog();
tommy.makeSound(); // Woof! Woof!

现在有了公共类字段语法,我们就可以在类的构造函数之外定义类字段,JavaScript 将隐式调用 super。

复制代码
class Animal {}
class Dog extends Animal {
sound = 'Woof! Woof!'; // public class field
makeSound() {
console.log( this.sound );
}
}
// create instance
const tommy = new Dog();
tommy.makeSound(); // Woof! Woof!

当 JavaScript 隐式调用 super 时,它会在实例化类时传递用户提供的所有参数(这是标准的 JavaScript 行为,与私有类字段无关)。因此,如果你的父构造函数需要定制参数,请确保手动调用 super。

复制代码
class Animal {
constructor( ...args ) {
console.log( 'Animal args:', args );
}
}
class Dog extends Animal {
sound = 'Woof! Woof!'; // public class field
makeSound() {
console.log( this.sound );
}
}
// create instance
const tommy = new Dog( 'Tommy', 'Loves', 'Toys!' );
tommy.makeSound(); // Animal args: [ 'Tommy', 'Loves', 'Toys!' ]

支持范围——TC39:阶段 3;Chrome:72+;Node:12+

私有类字段

众所周知,JavaScript 没有publicprivateprotected之类的属性修饰符。默认情况下,对象上的所有属性都是公共的,这意味着任何人都可以访问它们。想要定义一个对外界隐藏的属性,最接近的方法是使用 Symbol 这个属性名称。你可能会使用 _ 前缀来表示应该是私有的属性,但它只是一种表示法,不能解决问题。

现在有了私有类字段,我们可以让类属性只能在类中访问,并防止它们反映在实例(对象)上。来看前面的一个例子,先看一个暴露的属性。

复制代码
class Dog {
_sound = 'Woof! Woof!'; // this is private
makeSound() {
console.log( this._sound );
}
}
// create instance
const tommy = new Dog();
console.log( tommy._sound ); // Woof! Woof!

添加 _ 前缀并不能解决我们的问题。私有类字段的定义方式与定义公共类字段的方式相同,但我们不必添加下划线前缀,而是添加#前缀。访问对象上的私有属性将导致 SyntaxError: Undefined private field。

复制代码
class Dog {
#sound = 'Woof! Woof!'; // this is private
makeSound() {
console.log( this.#sound );
}
}
// create instance
const tommy = new Dog();
tommy.makeSound() // Woof! Woof!
//console.log( tommy.#sound ); // SyntaxError

私有属性只能在定义它们的类中访问。因此在父类或子类内无法访问私有属性。

我们还可以使用未定义的值定义私有(和公共)属性。

复制代码
class Dog {
#name;
constructor( name ) {
this.#name = name;
}
showName() {
console.log( this.#name );
}
}
// create instance
const tommy = new Dog( 'Tommy' );
tommy.showName(); // Tommy

支持范围——TC39:阶段 3;Chrome:74+;Node:12+

string.matchAll

我们在 string 数据类型上有 match 原型方法,它根据给定的正则表达式或关键字返回字符串中的匹配模式。

复制代码
const colors = "#EEE, #CCC, #FAFAFA, #F00, #000";
const matchColorRegExp = /([A-Z0-9]+)/g;
console.log( colors.match( matchColorRegExp ) );
// Output:
["EEE", "CCC", "FAFAFA", "F00", "000"]

但这种方法不提供其他附加信息,例如字符串中每个匹配的索引。删除 g 标志后可以生成其他信息,但之后我们只能获得第一个匹配项。

复制代码
const colors = "#EEE, #CCC, #FAFAFA, #F00, #000";
const matchColorRegExp = /#([A-Z0-9]+)/;
console.log( colors.match( matchColorRegExp ) );
// Output: (result shortned for viewing purpose)
["#EEE", "EEE", index: 0, input: "<colors>"]

最后,我们需要在正则表达式对象和语法上使用.exec 方法,这样写起来没那么复杂。我们需要使用 while 循环,直到 exec 返回 null 为止。但要注意,exec 不会返回迭代器。

复制代码
const colors = "#EEE, #CCC, #FAFAFA, #F00, #000";
const matchColorRegExp = /#([A-Z0-9]+)/g;
// in strict mode,
// Uncaught ReferenceError: match is not defined
while( match = matchColorRegExp.exec( colors ) ) {
console.log( match );
}
// Output: (result shortned for viewing purpose)
["#EEE", "EEE", index: 0, input: "<colors>"]
["#CCC", "CCC", index: 6, input: "<colors>"]
["#FAFAFA", "FAFAFA", index: 12, input: "<colors>"]
["#F00", "F00", index: 21, input: input: "<colors>"]
["#000", "000", index: 27, input: input: "<colors>"]

为了解决这个问题,我们现在有了 matchAll 方法,它返回一个迭代器,并且这个迭代器的每次 next() 调用都会连续返回匹配的项。

复制代码
const colors = "#EEE, #CCC, #FAFAFA, #F00, #000";
const matchColorRegExp = /#([A-Z0-9]+)/g;
console.log( ...colors.matchAll( matchColorRegExp ) );
// Output: (result shortned for viewing purpose)
["#EEE", "EEE", index: 0, input: "<colors>"]
["#CCC", "CCC", index: 6, input: "<colors>"]
["#FAFAFA", "FAFAFA", index: 12, input: "<colors>"]
["#F00", "F00", index: 21, input: input: "<colors>"]
["#000", "000", index: 27, input: input: "<colors>"]

支持范围——TC39:阶段 4;Chrome:73+;Firefox:67+;Node:12+

命名捕获组

与其他语言相比,JavaScript 中捕获或捕获组的概念略有不同。每当我们在括号内放置一个正则表达式模式(lookahead 和 lookbehind 除外)时,它就会变成一个捕获组,所有匹配的模式都会反映在匹配的输出项中。

在前面的示例中,下面数组的第一项是正则表达式模式的完整匹配,而第二项是捕获组的匹配值。

复制代码
["#EEE", "EEE", index: 0, input: "<colors>"]

如果有多个捕获组,它们将连续显示在结果中。我们来看一个例子。

复制代码
const str = "My name is John Doe.";
const matchRegExp = /My name is ([a-z]+) ([a-z]+)/i;
const result = str.match( matchRegExp );console.log( result );
// error, if result is null
console.log( { firstName: result[1], lastName: result[2] } );
// Output:
["My name is John Doe", "John", "Doe", index: 0, input: "My name is John Doe.", groups: undefined]
{firstName: "John", lastName: "Doe"}

如上所示,输出的第一个元素是完整匹配的字符串,而第二个和第三个元素是捕获组的结果。

现在有了命名捕获组后,我们可以使用标签将单个捕获组结果保存在 groups 对象中。定义命名捕获组的语法是 (?$pattern)。

复制代码
const str = "My name is John Doe.";
const matchRegExp = /My name is (?<firstName>[a-z]+) (?<lastName>[a-z]+)/i;
const result = str.match( matchRegExp );
console.log( result );
console.log( result.groups );
// Output:
["My name is John Doe", "John", "Doe", index: 0, input: "My name is John Doe.", groups: {firstName: "John", lastName: "Doe"}]
{firstName: "John", lastName: "Doe"}

命名捕获组也适用于 matchAll 方法。

支持范围——TC39:阶段 4;Chrome:64+;Node:10+

数字分隔符

我们写较大的整数或小数时,可读性一直是个大问题。例如,十亿写成数字是 1000000000,但你得数对零的个数才行,很多时候这都很让人头疼。

在较新版本的 JavaScript 中,我们可以使用 _ 分隔符来分隔数字的各个部分,以增强可读性。

复制代码
var billion = 1_000_000_000;
console.log( billion ); // 1000000000

我们可以随意将 _ 放在数字中,而 JavaScript 只会忽略它。这种方法适用于任何类型的数字,无论是整数、十进制、二进制、十六进制还是八进制数字都行。

复制代码
console.log( 1_000_000_000.11 ); // 1000000000.11
console.log( 1_000_000_000.1_012 ); // 1000000000.1012
console.log( 0xFF_00_FF ); // 16711935
console.log( 0b1001_0011 ); // 147
console.log( 0o11_17 ); // 591

支持范围——TC39:阶段 3;Chrome:75+;Node:12.5+

BigInt

JavaScript 中的数字是从 Number 函数(也是构造函数)创建的。一个数字可以可靠表示的最大值是(2⁵³ - 1),也就是 9007199254740991。也可以使用 Number.MAX_SAFE_INTEGER 生成这个数。

当我们写下数字时,JavaScript 用 Number 构造函数包装它,以生成一个在其原型上包含数字方法的对象。所有原始数据类型都会这样处理。参阅 Primitives vs Objects 这篇文章来理解这个理念。

那么如果我们继续加大这个数字会怎么样?

复制代码
console.log( Number.MAX_SAFE_INTEGER ); // 9007199254740991
console.log( Number.MAX_SAFE_INTEGER + 10 ); // 9007199254741000

上面代码中的最后一个日志输出返回了错误的结果。发生这种情况是因为 JavaScript 无法计算超过 Number.MAX_SAFE_INTEGER 值的数字。

现在有了BigInt就能解决这个问题了。BigInt 能让我们表示一个比 Number.MAX_SAFE_INTEGER 值更高的整数。与 Number 类似,BigInt 同时表现为一个函数和一个构造函数。加入 BigInt 后,JavaScript 有了新的 bigint 内置原始数据类型来表示大整数。

复制代码
var large = BigInt( 9007199254740991 );
console.log( large ); // 9007199254740991n
console.log( typeof large ); // bigint

JavaScript 会在整数的末尾添加 n 下标以表示 BigInt 整数形式。因此我们只需在整数的最末尾附加 n 就能写成 BigInt 了。

现在我们有了 BigInt,就可以安全地对具有 bigint 数据类型的大数字执行数学运算了。

复制代码
var large = 9007199254740991n;
console.log( large + 10n ); // 9007199254741001n

使用 number 数据类型的数字与使用 bigint 数据类型的数字不同,因为 bigint 只能表示整数。因此程序不允许 bigint 和 number 数据类型之间的算术运算。

BigInt 函数可以接受任何类型的数字,如整数、二进制、十六进制、八进制等。它会在内部统一转换为十进制。

BigInt 还支持数字分隔符。

复制代码
var large = 9_007_199_254_741_001n;
console.log( large ); // 9007199254741001n

支持范围——TC39:阶段 3;Chrome:67+;Firefox:68+;Node:10.4+

数组:flat 和 flatMap

数组对象上的 flat 和 flatMap 原型方法。

Array.flat

我们现在能在一个数组使用一个新的 flat(n) 原型方法,它将数组展平到第 n 个深度并返回一个新数组。默认情况下 n 为 1。我们可以将 n 作为 Infinity 传递,以展平所有嵌套数组。

复制代码
var nums = [1, [2, [3, [4, 5]]]];
console.log( nums.flat() ); // [1, 2, [3, [4,5]]]
console.log( nums.flat(2) ); // [1, 2, 3, [4,5]]
console.log( nums.flat(Infinity) ); // [1, 2, 3, 4, 5]

支持范围——TC39:阶段 4;Chrome:69+;Firefox:62+;Node:12+

Array.flatMap

日常编程工作中有时可能会使用 map 变换数组,然后将其展平。例如计算一些整数的平方。

复制代码
var nums = [1, 2, 3];
var squares = nums.map( n => [ n, n*n ] )
console.log( squares ); // [[1,1],[2,4],[3,9]]
console.log( squares.flat() ); // [1, 1, 2, 4, 3, 9]

我们可以使用 flatMap 原型方法,用一个语法同时执行映射和展平。它只能将从回调函数返回的数组展平到 1 的深度。

复制代码
var nums = [1, 2, 3];
var makeSquare = n => [ n, n*n ];
console.log( nums.flatMap( makeSquare ) ); // [1, 1, 2, 4, 3, 9]

支持范围——TC39:阶段 4;Chrome:69+;Firefox:62+;Node:11+

对象:fromEntries

我们可以使用对象的 entries 静态方法提取对象的 key:value 对,该方法返回一个数组,其中每个元素都是一个数组,后者的第一项是 key,第二项是 value。

复制代码
var obj = {x:1,y:2,z:3};
var objEntries = Object.entries(obj);
console.log(objEntries); // [[“x”,1],[“y”,2],[“z”,3]]

我们现在可以在对象上使用 fromEntries 静态方法,它会将条目转换回对象。

复制代码
var entries = [["x", 1],["y", 2],["z", 3]];
var obj = Object.fromEntries( entries );
console.log( obj ); // {x: 1, y: 2, z: 3}

之前我们使用 entries 就能很容易地过滤和映射对象值,但将条目放回到对象表单却很麻烦。这里就可以使用 fromEntries 简化工作了。

复制代码
var obj = { x: 1, y: 2, z: 3 };
// [["x", 1],["y", 2],["z", 3]]
var objEntries = Object.entries( obj );
// [["x", 1],["z", 3]]
var filtered = objEntries.filter(
( [key, value] ) => value % 2 !== 0 // select odd
);
console.log( Object.fromEntries( filtered ) ); // {x: 1, z: 3}

当我们使用 Map 按插入顺序存储键值对时,内部数据结构与条目格式类似。我们可以使用 fromEntries 轻松地从 Map 构造一个对象。

复制代码
var m = new Map([[“x”,1],[“y”,2],[“z”,3]]);
console.log(m); // {“x”=> 1,“y”=> 2,“z”=> 3}
console.log(Object.fromEntries(m)); // {x:1,y:2,z:3}

支持范围——TC39:阶段 4;Chrome:73+;Firefox:63+;Node:12+

globalThis

之前我们很熟悉 JavaScript 中的 this 关键字。它没有确定的值,其值取决于访问它的上下文。在任何环境中,当从程序的最顶层上下文访问时 this 指向全局对象,这就是所谓的全局 this。

例如,在 JavaScript 中全局 this 是 window 对象,你可以在 JavaScript 文件的顶部(最外层上下文)或 JavaScript 控制台内添加 console.log(this) 语句来验证这一点。

this 全局值在 Node.js 内部会指向 global 对象,而在 web worker 内部会指向 web worker 本身。但是要获取全局 this 值不太容易,因为我们不能在所有位置使用 this 关键字;例如,在类构造函数中 this 值指向类实例。

因此其他环境为我们提供了像 self 这样的关键字,与 JavaScript 和 web worker 中的全局 this 一样;而在 Node.js 中使用的是 global。使用这些替代关键字时,我们可以创建一个通用函数来返回全局 this 值。

复制代码
const getGlobalThis = () => {
if (typeof self !== 'undefined') return self;
if (typeof window !== 'undefined') return window;
if (typeof global !== 'undefined') return global;
if (typeof this !== 'undefined') return this;
throw new Error('Unable to locate global `this`');
};
var globalThis = getGlobalThis();

但是用这个 polyfill 获取全局 this 对象会出问题,这篇文章解释了原因。为了解决这个问题,JavaScript 现在提供了 globalThis 关键字,它可以从任何地方返回全局 this 对象。

复制代码
var obj = { fn: function() {
console.log( 'this', this === obj ); // true
console.log( 'globalThis', globalThis === window ); // true
} };
obj.fn();

支持范围——TC39:阶段 3;Chrome:71+;Firefox:65+;Node:12+

稳定排序

我们对数组排序时,ECMAScript 不会为 JavaScript 引擎提出排序算法,而只会强制执行排序 API 的语法。因此排序性能和 / 或排序稳定性会随着浏览器或 JavaScript 引擎的不同而变化。

但现在 ECMAScript 强制数组排序算法保持稳定。这个答案介绍了排序稳定性更新。简而言之,如果排序结果(变异数组)中那些不受排序影响的项目顺序不变,与一开始插入的顺序一致,则排序算法就是稳定的。我们来看一个例子吧。

复制代码
var list = [
{ name: 'Anna', age: 21 },
{ name: 'Barbra', age: 25 },
{ name: 'Zoe', age: 18 },
{ name: 'Natasha', age: 25 }
];
// possible result
[
{ name: 'Natasha', age: 25 }
{ name: 'Barbra', age: 25 },
{ name: 'Anna', age: 21 },
{ name: 'Zoe', age: 18 },
]

如上所示,在 list 数组中,名为 Barbra 的对象位于名为 Natasha 的对象之前。由于这些对象有着相同的年龄,我们希望排序结果中它们保持相同的顺序,但有时结果并非如此。排序算法的结果会取决于你使用的 JavaScript 引擎。

但是现在,所有现代浏览器和 Node.js 默认使用 sort 方法进行稳定排序。这将始终产生以下结果。

复制代码
// stable sort result
[
{ name: 'Barbra', age: 25 },
{ name: 'Natasha', age: 25 }
{ name: 'Anna', age: 21 },
{ name: 'Zoe', age: 18 },
]

一些 JavaScript 引擎以前支持稳定排序,但仅适用于较小的数组。为了提高大型数组的性能,他们可能会使用更快的算法并牺牲排序稳定性。

支持范围——Chrome:70+;Firefox:62+;Node:12+

国际化 API

国际化 API 是由 JavaScript 中的 ECMAScript 标准提供的 API,用于格式化指定语言中的数字、字符串、日期和时间。此 API 在 Intl 对象上可用。此对象提供构造函数,以便为指定的区域设置创建与区域相关数据的格式化程序。可在此处查看支持的区域设置列表。

Intl.RelativeTimeFormat

在许多应用程序中,我们通常需要以相对格式显示时间,例如5 分钟前、昨天1 周前等。当我们的网站需要区分不同区域的显示内容时,我们需要在分发包中存放所有可能的相对时间输出组合 。

JavaScript 现在在 Intl 对象上提供了 RelativeTimeFormat9(locale, config) 构造函数,它允许你为特定的区域设置创建时间格式化程序。这将创建一个具有 format(value, unit) 原型方法的对象来生成时间格式。

复制代码
// español (spanish)
var rtfEspanol= new Intl.RelativeTimeFormat('es', {
numeric: 'auto'
});
log( rtfEspanol.format( 5, 'day' ) ); // dentro de 5 días
log( rtfEspanol.format( -5, 'day' ) ); // hace 5 días
log( rtfEspanol.format( 15, 'minute' ) ); // dentro de 15 minutos

支持范围——TC39:阶段 3;Chrome:71+;Firefox:65+;Node:12+

Intl.ListFormat

ListFormat API 允许我们将列表中的项目基于 and 或 or 格式组合在一起。例如,[apples,mangoes,bananas] 使用并列格式就是 apples,mangoes and bananas,使用分离格式就是 apples,mangoes or bananas。

首先,我们需要根据区域环境从 ListFormat(locale, config) 构造函数创建格式化程序实例,并使用 format(list) 原型方法生成特定于区域环境的列表格式。

复制代码
// español (spanish)
var lfEspanol = new Intl.ListFormat('es', {
type: 'disjunction'
});
var list = [ 'manzanas', 'mangos', 'plátanos' ];
log( lfEspanol.format( list ) ); // manzanas, mangos o plátanos

支持范围——TC39:阶段 3;Chrome:72+;Node:12+

Intl.Locale

除了语种名称外,区域设置通常还有很多内容,如日历类型、小时制、语言等。Intl.Locale(localeId, config) 构造函数用来基于提供的配置生成格式化的语言环境字符串。它创建的对象包含所有区域设置属性,并暴露 toString 原型方法以获取格式化的区域设置字符串。

复制代码
const krLocale = new Intl.Locale( 'ko', {
script: 'Kore', region: 'KR',
hourCycle: 'h12', calendar: 'gregory'
} );
log( krLocale.baseName ); // ko-Kore-KR
log( krLocale.toString() ); // ko-Kore-KR-u-ca-gregory-hc-h12

在此处( https://unicode.org/reports/tr35/#unicode_locale_id )了解区域设置标识符和 Unicode 区域设置标记。

支持范围——TC39:阶段 3;Chrome:74+;Node:12+

Promise

之前,我们在 Promise 构造函数上有 all 和 race 两种静态方法。Promise.all([… promises]) 返回一个 promise,它在输入的所有promises 解析后才解析,输入的任何promise 被拒绝时它也会被拒绝。Promise.race([… promises]) 返回一个 promise,输入的任何 promise 解析后它就会解析,输入的任何 promise 被拒绝后它也会被拒绝。

我们迫切需要一个静态方法来返回一个 promise,它要在所有 promise 完成后(解析或拒绝)解析。我们还需要一个类似 race 的方法来返回一个 promise,等输入的任何 promise 解析后它就会解析。

Promise.allSettled

Promise.allSettled 方法获取一组 promise,并在所有 promise 都被解析或拒绝后解析。因此,此方法返回的 promise 不需要 catch 回调,因为它总是会解析。then 回调按照各个 promise 的顺序接收每个 promise 的 status 和 value。

复制代码
var p1 = () => new Promise(
(resolve, reject) => setTimeout( () => resolve( 'val1' ), 2000 )
);
var p2 = () => new Promise(
(resolve, reject) => setTimeout( () => resolve( 'val2' ), 2000 )
);
var p3 = () => new Promise(
(resolve, reject) => setTimeout( () => reject( 'err3' ), 2000 )
);
var p = Promise.allSettled( [p1(), p2(), p3()] ).then(
( values ) => console.log( values )
);
// Output
[ {status: "fulfilled", value: "val1"}
{status: "fulfilled", value: "val2"}
{status: "rejected", value: "err3"}
]

支持范围——TC39:阶段 3;Chrome:76+

Promise.any

Promise.any 方法类似于 Promise.race,但是只要任何 promise 被拒绝,后者返回的 promise 就不会执行 catch 块。相比之下,前者等任何 promise 解析后它返回的 promise 也会解析。如果没有解析任何 promise,catch 块将被执行。如果有任何 promise 先解析了,就会执行 then 块。

支持范围——TC39:阶段 1

观看本文视频

英文原文: https://itnext.io/whats-new-in-javascript-google-i-o-2019-summary-d16bd230841

收藏

评论

微博

发表评论

注册/登录 InfoQ 发表评论