原文链接
很难跟踪JavaScript(ECMAScript)中的新功能,更难找到有用的代码示例。
因此,在本文中,我将介绍在ES2016,ES2017和ES2018(最终草案)中添加的TC39已完成提案中列出的所有18个功能,并向他们展示有用的示例。
这是一个相当长的帖子,但应该是一个简单的阅读。把这看作是“Netflix binge reading”。在这之后,我保证你将对这些功能有很多的了解。
让我们一个接一个地看看:
includes
是数组上的简单实例方法,并有助于轻松查找某个项是否在Array中(包括 NaN
不像 indexOf
)
const arr = [ 1, 2, 3, 4, NaN]; // es2016不使用 if(arr.indexOf(3) >= 0){ console.log(true); } //使用 if(arr.includes(3)){ console.log(true); } //ps: 注意 indexOf 是不支持查找NaN的 arr.includes(NaN) // true arr.indexOf(NaN) // -1 (indexOf 不支持 NaN)
琐事:人们想要命名的JavaScript规范 contains
,但这显然已被Mootools使用,因此他们使用了 includes
。
数学运算如加法和减法分别有 +
和 -
等运算符。与他们类似, **
运算符通常用于指数运算。在ECMAScript 2016中,引入了 **
而不是Math.pow。
// 不使用 Math.pow(7,2) //49 //使用 7**2 //49
Object.value()
是一个与 Object.keys()
类似的新函数,但会返回Object自身属性的所有值,排除原型链中的任何值。
const cars = { 'BMW': 3, 'Tesla': 2, 'Toyota': 1 }; //ES2015 //ES2017 不使用 const vals = Object.keys(cars).map(key => cars[key]); console.log(vals); //[3, 2, 1] //ES2017 and 未来 //使用 const values = Object.values(cars); console.log(values); // [3, 2, 1]
Object.entries()
与 Object.keys
相关,但不是仅返回keys,而是以数组方式返回keys和values。这使得像循环中使用对象或将对象转换为Maps这样的事情变得非常简单。
例 1:
const cars = { 'BMW': 3, 'Tesla': 2, 'Toyota': 1 }; // ES5.1 //而不是提取键然后再循环 Object.keys(cars).forEarch(function(key) { console.log('key: ' + key + 'value: ' + cars[key]); }) //ES2017 (ES8) //使用 Object.entries for(let [key value] of Object.entries(cars)) { console.log(`key: ${key} value: ${value}`); }
例 2:
const cars = { 'BMW': 3, 'Tesla': 2, 'Toyota': 1 }; //ES2015 //不使用 //获取对象键,然后添加每个项目以在循环中映射 const maps = new Map(); Object.keys(cars).forEarch(key => { map1.set(key, cars[key]); }) console.log(map1) // Map { 'BMW' => 3, 'Tesla' => 2, 'Toyota' => 1} //ES2017 and 以后 //使用 const map = new Map(Object.entries(cars)); console.log(map) //Map { 'BMW' => 3, 'Tesla' => 2, 'Toyota' => 1}
向字符串 – String.prototype.padStart
和 String.prototype.padEnd
添加了两个实例方法 – 它们允许在原始字符串的开始或结尾附加/预先添加空字符串或其他字符串。
'someString'.padStart(numberOfCharcters [,stringForPadding]); '5'.padStart(10) // ' 5' '5'.padStart(10, '=*') //'=*=*=*=*=5' '5'.padEnd(10) // '5 ' '5'.padEnd(10, '=*') //'5=*=*=*=*='
当我们想要在诸如漂亮的打印显示或终端打印等场景中对齐事物时,这很方便。
padStart(10, '0')
轻松实现这一点。 //ES2017 //如果你有一个不同长度的项目列表,并希望格式化它们的显示目的,你可以使用padStart const formatted = [0, 1, 12, 123, 1234, 12345].map(num => num.toString().padStart(10, '0') // 添加 0 直到长度为 10 ); console.log(formatted); //打印 // [ // '0000000000', // '0000000001', // '0000000012', // '0000000123', // '0000001234', // '0000012345', // ]
我们打印多个不同长度的项目并想要正确对齐它们时, padEnd
真的很方便。
下面的例子是 padEnd
, padStart
和O bject.entries
如何聚合在一起产生漂亮输出的一个很好的实例。
const cars = { ':blue_car:BMW': '10', ':oncoming_automobile:Tesla': '5', ':oncoming_taxi:Lamborghini': '0' } Object.entries(cars).map(([name, count]) => { //padEnd appends ' -' until the name becomes 20 characters //padStart prepends '0' until the count becomes 3 characters. console.log(`${name.padEnd(20, ' -')} Count: ${count.padStart(3, '0')}`) }); //打印结果.. // :blue_car:BMW - - - - - - - Count: 010 // :oncoming_automobile:Tesla - - - - - - Count: 005 // :oncoming_taxi:Lamborghini - - - Count: 000
Emojis和其他双字节字符使用unicode的多个字节表示。所以 padStart
和 padEnd
可能无法按预期工作!:warning:
例如:假设我们正在尝试用:heart:表情符号填充字符串 heart
以达到10个字符。结果如下所示
//注意,不是5颗心,只有2颗心和1颗看起来很奇怪的心! 'heart'.padStart(10, ":heart:"); // prints.. ':heart::heart:❤heart'
这是因为:heart:长2个字符( '/u2764/uFE0F'
)!单词heart本身是5个字符,所以我们只剩下5个字符。所以,JS 填塞两颗心用 '/u2764/uFE0F'
和生产:heart::heart:。为最后一个只是用心灵的第一个字节 / u2764
产生❤
所以结果是: :heart::heart:❤heart
PS:您可以使用 此链接 查看unicode字符转换
此方法返回给定对象的所有属性的所有详细信息(包括 get
set
方法). 添加这个的主要动机是允许浅拷贝/克隆一个对象到另一个对象中,这个对象也拷贝getter和setter函数,而不是 Object.assign
以下示例显示了 Object.assign
和 Object.getOwnPropertyDescriptors
与 Object.defineProperties
之间的区别,以将原始对象 Car
复制到新对象 ElectricCar
, Object.getOwnPropertyDescriptors
看到, discount
获取器和设置器函数也被复制到目标对象中。
######之前…
//之前... var Car = { name: 'BMW', price: 1000000, set discount(x) { this.d = x; }, get discount() { return this.d; }, }; //打印Car对象的“discount”属性的详细信息 console.log(Object.getOwnPropertyDescriptor(Car, 'discount')); //打印结果.. // { // get: [Function: get], // set: [Function: set], // enumerable: true, // configurable: true // } //使用Object.assign将Car的属性复制到ElectricCar const ElectricCar = Object.assign({}, Car); //打印ElectricCar对象的“discount”属性的详细信息 console.log(Object.getOwnPropertyDescriptor(ElectricCar, 'discount')); //打印 // { // value: undefined, // writable: true, // enumerable: true, // configurable: true // } //:warning: 请注意ElectricCar对象中的“discount”属性中缺少getter和setter!:-1::-1:
######以后…
//以后... var Car = { name: 'BMW', price: 1000000, set discount(x) { this.d = x; }, get discount() { return this.d; }, }; //使用Object.defineProperties将Car的属性复制到ElectricCar2 //并使用Object.getOwnPropertyDescriptors提取Car的属性 const ElectricCar2 = Object.defineProperties({}, Object.getOwnPropertyDescriptors(Car)); //打印ElectricCar2对象“discount”属性的详细信息 console.log(Object.getOwnPropertyDescriptor(ElectricCar2, 'discount')); //prints.. // { get: [Function: get], :point_left|type_3::point_left|type_3::point_left|type_3: // set: [Function: set], :point_left|type_3::point_left|type_3::point_left|type_3: // enumerable: true, // configurable: true // } // 请注意,getter和setter存在于ElectricCar2对象中,用于'discount'属性!
这是一个小的更新,它允许我们在最后一个函数参数后面加上尾随逗号。为什么?为了帮助像git这样的工具,只会让新开发人员受到指责。
以下示例显示了问题和解决方案:
//问题 // #1 开发者创建 function Person( name, age ) { this.name = name; this.age = age; } //#2 开发者添加address function Person( name, age, // 添加尾随逗号 <--------因为逗号,开发人员会被git等工具所警告 address //添加新的参数 ) { this.name = name; this.age = age; this.address = address; //添加的这行 } //ES2017 解决 //允许#1开发者添加尾随逗号 //#1 创建如下 function Person( name, ange, //<------- 由于尾随逗号,#2开发者不需要改变这行 ) { this.name = name; this.age = age; }
注意:您也可以使用尾随逗号来调用函数
如果你问我,这是迄今为止最重要也是最有用的功能。异步函数(Async)允许我们不处理调用地狱,并使整个代码看起来很简单。
async
关键字告诉JavaScript编译器以不同的方式处理函数,编译器在该函数内到达 await
关键字时暂停。它假设在 await
之后的表达式返回一个承诺,并等待承诺解决或拒绝后继续前进.
在下面的例子中, getAmount
函数调用两个异步函数 getUser
和 getBankBalance
。我们可以承诺做到这一点,但使用 async await
更优雅和简单
//不使用 // ES2015 Promise function getAmount(userId) { getUser(userId) .then(getBankBalance) .then(amount => { console.log(amount); }); } //使用... // ES2017 async function getAmount2(userId) { var user = await getUser(userId); var amount = await getBankBalance(user); console.log(amount); } getAmount('1'); //$1,000 getAmount2('1'); //$1,000 function getUser(userId) { return new Promise(resolve => { setTimeout(() => { resolve('john') }, 1000) }); } function getBankBalance(user) { return new Promise((resolve, reject) => { setTimeout(() => { if(user == 'john') { resolve('$1,000'); } else { reject(unknown user); } }, 1000) }); }
如果您正在等待异步函数的结果,则需要使用Promise的 then
语法来捕获其结果。
在下面的例子中,我们想使用c onsole.log
记录结果,但不是在doubleAndAdd中。所以我们想等待并使用 then
语法将结果传递给 console.log
.
//异步函数本身返回一个Promise async function doubleAndAdd(a, b) { a = await doubleAfterlSec(a); b = await doubleAfterlSec(b); return a + b; } //用法 doubleAndAdd(1, 2).then(console.log); function doubleAfterlSec(param) { return new Promise (resolve => { setTimeout(resolve(param * 2), 1000); }); }
在前面的例子中,我们调用等待两次,但每次我们等待一秒(总共2秒)。相反,我们可以让它并行,因为 a
和 b
不依赖于使用 Promise.all
的其他对象。
//异步函数本身返回一个Promise async function doubleAndAdd(a, b) { //注意:这边使用 Promise.all //注意到使用数组解构,捕获结果 [a, b] = await Promise.all([doubleAfterlSec(a), doubleAfterlSec(b)]); return a + b; } //用法 doubleAndAdd(1, 2).then(console.log); function doubleAfterlSec(param) { return new Promise (resolve => { setTimeout(resolve(param * 2), 1000); }); }
下面使用异步等待时有各种方法来处理错误。
Option 1 - 在函数中使用try catch
// 1. 使用 try catch async function doubleAndAdd(a, b) { try { a = await doubleAfterlSec(a); b = await doubleAfterlSec(b); } catch (e) { return NaN; // return something } return a + b; } // 用法 doubleAndAdd('one', 2).then(console.log) //NaN doubleAndAdd(1, 2).then(console.log) // 6 function doubleAfterlSec(param) { return new Promise((resolve, reject) => { setTimeout(function() { let val = param * 2; isNaN(val) > reject(NaN) : resolve(val); }, 1000); }); }
Option 2 — catch 获每个等待表达式(Catch every await expression)
由于每个 await
表达式都会返回 Promise
,因此您可以在每行上捕获错误,如下所示。
//Option 2 - *Catch* 每个等待线上的错误 //因为每个等待表达本身就是一个Promise async function doubleAndAdd(a, b) { a = await doubleAfter1Sec(a).catch(e => console.log('"a" is NaN')); // :point_left: b = await doubleAfter1Sec(b).catch(e => console.log('"b" is NaN')); // :point_left: if (!a || !b) { return NaN; } return a + b; } //用法: doubleAndAdd('one', 2).then(console.log); // NaN and logs: "a" is NaN doubleAndAdd(1, 2).then(console.log); // 6 function doubleAfter1Sec(param) { return new Promise((resolve, reject) => { setTimeout(function() { let val = param * 2; isNaN(val) ? reject(NaN) : resolve(val); }, 1000); }); }
Option 3 — 捕获整个异步等待函数(Catch the entire async-await function)
//Option 3 - 不要做任何事情,在function外捕获 //因为异步/等待返回一个Promise,我们可以捕捉整个函数的错误 async function doubleAndAdd(a, b) { a = await doubleAfter1Sec(a); b = await doubleAfter1Sec(b); return a + b; } //用法 doubleAndAdd('one', 2) .then(console.log) .catch(console.log); // :point_left::point_left|type_3:<------- use "catch" function doubleAfter1Sec(param) { return new Promise((resolve, reject) => { setTimeout(function() { let val = param * 2; isNaN(val) ? reject(NaN) : resolve(val); }, 1000); }); }
ECMAScript目前处于最终草案中,将于2018年6月或7月发布。以下所有功能均在Stage-4中,并将成为ECMAScript 2018的一部分
主要思想是为JavaScript提供某种多线程功能,以便JS开发人员可以编写高性能,通过允许自己管理内存而不是让JS引擎管理内存来实现并发程序。
这是通过一种名为 SharedArrayBuffer 的新型全局对象来完成的,它基本上将数据存储在共享内存空间中.所以这个数据可以在主JS线程和网络工作者线程之间共享。
到现在为止,如果我们想要在主JS线程和Web工作者之间共享数据,我们必须复制数据并使用 postMessage
将其发送到其他线程。就这样子!
您只需使用 SharedArrayBuffer
,主线程和多个Web工作线程即可访问数据。
但在线程之间共享内存可能会导致竞争状况。为了避免竞争条件,引入了“ Atomics
”全局对象。当一个线程正在使用它的数据时, Atomics
提供了各种方法来锁定共享内存。它还提供了安全地更新共享内存中的数据的方法。
建议通过某个库使用此功能,但现在没有构建在此功能之上的库。
如果您有兴趣,推荐阅读:
首先,我们需要澄清一下“Tagged Template literal”是什么,这样我们可以更好地理解这个特性。
在ES2015+中,有一项称为标记模板文字的功能,允许开发人员自定义如何插入字符串。例如,以标准方式插入字符串,如下所示
//标准字符串插值 const firstName = 'Raja'; const greetings = `Hello ${firstName}!`; console.log(greetings); // "Hello Raja!"
在标记字面量中,可以编写一个函数来接收字符串文字的硬编码部分,例如 ['Hello','!']
和替换变量,例如[ 'Raja']
,作为参数写入自定义函数(例如 greet
),然后从该自定义函数中返回所需的任何内容。
下面的例子显示我们的自定义“Tag”函数 greet
“Good Morning!”,“Good afternoon,”等日子的时间,依此类推,取决于字符串文字的一天中的时间,并返回一个自定义字符串。
// "标记”函数返回一个自定义字符串文字. // 在这个例子中,greet调用timeGreet()来追加Good // Morning/Afternoon/Evening 视当天的时间而定. function greet(hardCodedPartsArray, ...replacementPartsArray) { console.log(hardCodedPartsArray); //[ 'Hello ', '!' ] console.log(replacementPartsArray); //[ 'Raja' ] let str = ''; hardCodedPartsArray.forEach((string, i) => { if (i < replacementPartsArray.length) { str += `${string} ${replacementPartsArray[i] || ''}`; } else { str += `${string} ${timeGreet()}`; //<-- 插入 Good morning/afternoon/evening here } }); return str; } //用法 const firstName = 'Raja'; const greetings = greet`Hello ${firstName}!`; //:point_left|type_3:<-- 标记文字 console.log(greetings); //'Hello Raja! Good Morning!' :fire: function timeGreet() { const hr = new Date().getHours(); return hr < 12 ? 'Good Morning!' : hr < 18 ? 'Good Afternoon!' : 'Good Evening!'; }
现在我们已经讨论了“标记”功能是什么,许多人想要在不同的域中使用这个功能,比如在终端中使用命令和HTTP请求来编写URI等等。
:warning: 标记字符串文字的问题
问题是ES2015和ES2016规范不允许使用“ /u
”(unicode),“ /x
”(十六进制)等转义字符,除非它们看起来完全像 /u00A9
或 /uA9A
或 /xA9
。
因此,如果您有一个内部使用其他域规则(如终端规则)的Tagged函数,那么可能需要使用 /ubla123abla
,它看起来不像 /u0049
或 /u{@F804}
,那么您将得到语法错误。
在ES2018中,只要标记函数返回具有“cooked”属性(其中无效字符为“undefined”)的对象中的值,然后将“raw”属性(与任何你想要的)。
function myTagFunc(str) { return { "cooked": "undefined", "raw": str.raw[0] } } var str = myTagFunc `hi /ubla123abla`; //调用 myTagFunc str // { cooked: "undefined", raw: "hi //unicode" }
目前在正则中,虽然dot(“.”)应该匹配单个字符,但它不匹配像 /n /r /f
等新行字符。
比如:
//之前 /first.second/.test('first/nsecond'); //false
此增强功能使点运算符可以匹配任何单个字符。为了确保这不会破坏任何内容,我们需要在创建正则时使用 /s
标志以使其正常工作。
//ECMAScript 2018 /first.second/s.test('first/nsecond'); //true Notice: /s :point_left|type_3:
以下是 提案 文档中的整体API:
const re = /foo.bar/s; //或者 const re = new RegExp('foo.bar', 's'); re.test('f00/nbar'); //true re.dotAll // true re.flags // 's'
这种增强功能为其他语言(如Python,Java等)提供了有用的RegExp功能,称为“命名组”。该功能允许开发人员编写RegExp以不同部分的格式(?
(?<year>) (?<month>) and (?year)
名称对日期RegEx的不同部分进行分组。结果对象现在将包含具有属性 year
, month
和 year
的具有相应值的 groups
属性。 //之前 let re1 = /(/d{4})-(/d{2})-(/d{2})/; let result1 = re1.exec('2015-01-02'); console.log(result1); // //["2015-01-02", "2015", "01", "02", index: 0, input: "2015-01-02", groups: undefined] //以后(ES2018) let re2 = /(?<year>/d{4})-(?<month>/d{2})-(?<day>/d{2})/u; let result2 = re2.exec('2015-01-02'); console.log(result2); //["2015-01-02", "2015", "01", "02", index: 0, input: "2015-01-02", groups: { year: '2015', month: '01', day: '02'}] //用法: //你想要知道年份,你可以: console.log(result2.groups.year) //2015
我们可以使用 /k<group name>
格式在正则表达式本身中反向引用该组。以下示例显示了它的工作原理。
//下面例子,有个组叫“fruit” //它既能匹配“apple”又能匹配“orange”.我们可以使用“/k<group name>”来回引用这个组的结果,(/k<fruit>) //所以它匹配==两侧的同一个单词 let sameWords = /(?<fruit>apple|orange)==/k<fruit>/u; sameWords.test('apple==apple'); //true sameWords.test('orange==orange'); //true sameWords.test('apple==orange'); //false
现在,已命名的组功能被烘焙到String的 replace
实例方法中。所以我们可以很容易地交换字符串中的单词。
例如,更改“firstName, lastName” ==> “lastName, firstName”
// 更换“firstName, lastName” ==> “lastName, firstName” let re = /(?<firstName>[A-Za-z]+) (?<lastName>[A-Za-z]+$)/u; 'Raja Rao'.replace(re, '$<lastName>, $<firstName>'); // "Rao, Raja"
rest运算符 ...
(三个点)允许我们提取尚未提取的对象属性。
// 提取 firstName 和 age // 将其余的项存储在“remaining”变量中。 let { fristName, age, ...remaining } = { fristName: 'john', lastName: 'smith', age: 20, height: '5.10', race: 'martian', }; firstName; //john age //20 remaining //{ lastName: 'smith', height: '5.10', race: 'martian' }
//假设我们想要移除SSN,我们不需要遍历整个对象并创建一个新的cleanObj。 // 简单地使用rest解构来提取出SSN,并将其余的保存在cleanObj中。 let { SSN, ...cleanObj } = { firstName: 'john', lastName: 'smith', SSN: '123-45-6789', race: 'martian', } cleanObj; //{ firstName: 'john', lastName: 'smith', race: 'martian' }
...
, 但区别在于您使用扩展来创建(重新构建)新对象。 提示:扩展操作符用于等号的右侧。Rest用于等号的左侧。
// person 和 account 合并 const person = { fName: 'john', age: 20 }; const account = { name: 'bofa', amount: '$1000' }; // 通过扩展运算符提取person和account的属性,并将其添加到一个新对象中。 const personAndAccount = { ...person, ...account }; personAndAccount; // {fName: 'john', age: 20, name: 'bofa', amount: '$1000' }
这是对RegExp的增强,它允许我们确保某些字符串立即 存在于 其他字符串之前。
您现在可以使用一个组( ?<=...
)(问号,小于,等于)来查找肯定的声明。
此外,您可以使用( ?<=...
)(问号,小于,感叹号)来查看否定声明。从本质上讲,只要-ve声明通过,就会匹配。
正面声明:假设我们想要确保#符号在单词 winning
之前存在(即: #winning
)并且希望正则表达式只返回字符串“winning”。这是你如何写它。
/(?<=#).*/.test('winning'); // false /(?<=#).*/.test('#winning'); // true //之前 '#winning'.match(/#.*/)[0]; // '#wining',包含# //ES2018后 '#winning'.match(/(?<=#).*/)[0]; // 'wining' 没有#, #是用来验证的。
否定声明:假设我们想从这些数字之前的具有€符号而不是$符号的行中提取数字。
// 3.00不能匹配,因为符号$在前面 'A gallon of milk is $3.00'.match(/(?</$)/d+/.?/d+/); // null // 2.43匹配成功,因为前面没有符号$ //当匹配时,不包含符号€ 'A gallon of milk is €3.00'.match(/(?</$)/d+/.?/d+/)[0]; //2.43
要编写RegExp来匹配各种Unicode字符并不容易。像 /w
, /W
, /d
等只能匹配英文字符和数字。但是对于印度语,希腊语等其他语言的数字呢?
这就是Unicode Property Escapes的用武之地。结果是,Unicode为每个符号(字符)添加了元数据属性,并使用它来对各种符号进行分组或描述各种符号。
例如,Unicode数据库将所有印地语字符(हिन्दी)组合在一个名为具有值 Devanagari
的 Script
脚本的属性下,另一个名为具有相同值 Devanagari
的 Script_Extensions
的属性 。所以我们可以搜索 Script=Devanagari
并获得所有印地文字符。
Devanagari(梵文)可用于马拉地语,北印度语,梵语等各种印度语言。
从ECMAScript 2018开始,我们可以使用 /p
转义字符和 {Script=Devanagari}
来匹配所有印度字符。
也就是说,我们可以在RegEx中使用: /p{Script=Devanagari}
来匹配所有梵文字符。
//他跟随匹配多个印地文字符 /^/p{Script=Devanagari}+$/u.test('हिन्दी'); //true //PS:有3个印地文字符h
同样,Unicode数据库将所有希腊字符组合为 Script_Extensions
(和 Script
)属性,其值为 Greek
。所以我们可以使用 Script_Extensions=Greek
或 Script=Greek
来搜索所有希腊字符。
/p{Script = Greek}
来匹配所有希腊字符。 //以下匹配一个希腊字符 //p{Script_Extensions=Greek}/u.test('π'); // true
此外,Unicode数据库将各种类型的 Emojis
(表情符号)存储在属性值为“true”的布尔属性 Emoji
, Emoji_Component
, Emoji_Presentation
, Emoji_Modifier
和 Emoji_Modifier_Base
下。所以我们可以通过简单地选择表情符号来查找所有表情符号。
/p{Emoji}
, /Emoji_Modifier
等来匹配各种Emojis。 下面的例子将会清楚。
// 下面是匹配表情字符 //p{Emoji}/u.test(':heart:'); //true // 下面的操作失败了,因为黄色的emojis没有Emoji_Modifier! //p{Emoji}/p{Emoji_Modifier}/u.test(':v:'); //false //下面的操作是匹配的,/p{Emoji} 紧跟后面是 /p{Emoji_Modifier} //p{Emoji}/p{Emoji_Modifier}/u.test('✌ '); //true // 解释: // 默认情况下,胜利表情是黄色的。如果我们使用相同表情的棕色、黑色或其他,它们被认为是原始表情的变体,并使用两个unicode字符表示。一个是原始表情符号,另一个是颜色的unicode字符。在下面的例子中,虽然我们只看到一个棕色的胜利表情符号,但它实际上使用了两个unicode字符,一个用于表情符号,另一个用于棕色。 //在Unicode数据库中,这些颜色具有Emoji_Modifier属性。因此,我们需要使用/p{Emoji}和/p{Emoji_Modifier}来正确和完全匹配棕色的表情符号。 //p{Emoji}/p{Emoji_Modifier}/u.test('✌ '); //true
最后,我们可以使用大写的“P”( /P
)转义字符而不是小p( /p
)来否定匹配。
参考:
ECMAScript 2018 Proposal
finally() 是添加到Promise的新实例方法。他的主要想法是允许在解决或拒绝之后运行回调以帮助清理事情。
finally
回调被调用时没有任何价值,并且无论如何总是被执行。
我们来看看各种情况:
// Resolve let started = true; let myPromise = new Promise(function(resolve, reject) { resolve('all good'); }) .then(val => { console.log(val); //all good }) .catch(e => { console.log(e)l // 跳过 }) .finally(() => { console.log('This function is always executed!'); started = false; // 清除 })
// Reject let started = true; let myPromise = new Promise(function(resolve, reject) { resolve('reject apple'); }) .then(val => { console.log(val); //reject apple }) .catch(e => { console.log(e)l //catch被跳过 }) .finally(() => { //注意这里没有任何值 console.log('This function is always executed!'); started = false; //清除 })
//错误情况1 //Promise 抛出错误 let started = true; let myPromise = new Promise(function(resolve, reject) { throw new Error('Error'); }) .then(val => { console.log(val); // 跳过 }) .catch(e => { console.log(e)l // 出现错误catch被调用 }) .finally(() => { // 注意这里没有任何值 console.log('This function is always executed!'); started = false; // 清除 })
// 错误从“catch”事件中抛出 let started = true; let myPromise = new Promise(function(resolve, reject) { throw new Error('something happend'); }) .then(val => { console.log(val); // 跳过 }) .catch(e => { throw new Error('throw another error') }) .finally(() => { // 注意这里没有任何值 console.log('This function is always executed!'); started = false; //清除 // 错误形式的捕获将需要在其他地方调用函数来处理 })
这是一个非常有用的功能。基本上它允许我们轻松创建异步代码循环!
这个特性增加了一个新的“for-await-of”循环,允许我们调用异步函数来返回Promise(或带有一系列Promise的数组)。很酷的是循环等待每个Promise在下一个循环前解析。
const promises = [ new Promise(resolve => resolve(1)), new Promise(resolve => resolve(2)), new Promise(resolve => resolve(3)), ] // 之前 // for-of使用常规同步迭代器 // 不等待Promise 去解决 async function test1() { for (const obj of promise) { console.log(obj) // 记录3 条Promise对象 } } // 以后 // for-wait-of使用异步迭代 async function test2() { for await (const obj of promises) { console.log(obj); // 1,2, 3 } } test1() // promise, promise, promise test2() // 1,2,3
本文作者: sachieyuan