ES2018 规范引入了四个新功能。这些功能包括异步迭代,rest/spread 属性,Promise.prototype.finally()
和正则表达式改进。本问将帮你了解这些 ES2018 功能的工做原理及使用方法。javascript
异步迭代是讨论的比较少 ES2018 功能之一。虽然还有不少关于 ES2018 其余功能的讨论,但几乎没有关于异步迭代这方面的内容。经过异步迭代,咱们能够获得异步的可迭代对象和迭代器。前端
这意味着你能够把 await
关键字与 for…of
循环放在一块儿使用。你能够用这些循环对可迭代对象进行迭代。可迭代对象的包括数组、map、set,NodeList,函数的 arguments 参数,TypedArray 等。java
在 ES2018 以前,for...of
循环是同步的。若是你试着迭代涉及异步操做的可迭代对象并 await
,则没法正常工做。循环自己会保持同步,基本上忽略 await
,并在其内部的异步操做能够完成以前完成迭代。git
// 下面的代码在 ES2018 以前不起做用,由于循环保持同步。 // 建立一个异步函数: async function processResponses(someIterable) { // 对可迭代对象进行迭代 for (let item of someIterable) { // 经过异步操做处理项目,例如promise: await processItem(item) } }
同时 for...of
循环也能够与异步代码一块儿使用。也就是说能够在遍历可迭代对象时执行一些异步操做。for...of
循环将会是异步的,让你可以等待异步操做完成。程序员
须要记住的是在哪里使用 await
关键字。不须要把它放进循环体中,应该将其放在for...of
关键字中 for
的后面。如今当你用 next()
方法获取异步迭代器的下个值时,将会获得一个 Promise
。若是你想了解更多信息,能够在 GitHub 上去看看(https://github.com/tc39/propo...)。github
// 建立一个异步函数: async function processResponses(someIterable) { //遍历可迭代对象并等待异步操做的结果 for await (let item of someIterable) { processItem(item) } }
rest
和 spread
并非真正的新功能。二者都是在 ES6 中做为新的运算符引入的,它们很快就开始流行起来。能够说 JavaScript 程序员喜欢它们。惟一的问题是它们只能用在数组和参数上,不过 ES2018 把这两个功能引入了对象中。面试
rest
和 spread
运算符的语法都很是简单,由三个点(...
)组成。这些点后面是要在其上使用 rest
或 spread
运算符的对象。接下来简单的讨论一下二者的工做原理。正则表达式
rest
运算符使你能够将对象的全部剩余对象属性属性提取到新对象上。要注意这些属性必须是可枚举的。若是你已经对某些属性使用了分解,那么 rest
运算符会只提取剩余的属性。数据库
// Rest example: const daysObj = { one: 'Monday', two: 'Tuesday', three: 'Wednesday', four: 'Thursday', five: 'Friday' } //使用解构将变量的前两个属性分配给变量。 //而后,使用rest将其他属性分配给第三个变量。 const { one, two, ...restOfDays } = daysObj // rest 仅提取 "three", "four" 和 "five" // 由于咱们已经提取了 "one" 和 "two" console.log(one) // Output: // 'Monday' console.log(two) // Output: // 'Tuesday' console.log(restOfDays) // Output: // { three: 'Wednesday', four: 'Thursday', five: 'Friday' }
若是要对对象使用 rest
运算符,须要记住两点:首先,只能用一次,除非把它用在嵌套对象上。其次,必须在最后使用。这就是为何在上面的例子中,在解构前两个属性以后而不是以前看到它的缘由。json
// 这行代码不起做用,由于把 rest 运算符用在了最前面: const { ...all, one, two } = { one: 1, two: 2, three: 3 } //这行能起做用: const { one, two, ...all } = { one: 1, two: 2, three: 3 } // 这行不起做用,由于同一级别上有多个 rest 运算符: const { one, ...some, ...end } = { /* some properties */ } // 这行能起做用,在多个级别上的多个 rest 运算符: const { one, {...secondLevel }, ...firstLevel } = { /* some properties */ }
spread 运算符的做用是能够经过插入另外一个对象的全部属性来建立新对象。 Spread 运算符还容许你从多个对象插入属性。也能够把这个运算符与添加新属性结合使用。
// Spread example: const myOriginalObj = { name: 'Joe Doe', age: 33 } // 用 spread 运算符建立新对象: const myNewObj = { ...myOriginalObj } console.log(myNewObj) // Output: // { name: 'Joe Doe', age: 33 } // 添加属性的例子: const myOriginalObj = { name: 'Caesar' } // 用 spread 运算符建立新对象 // 并添加新的属性“genre”: const myNewObj = { ...myOriginalObj, genre: 'Strategy' } console.log(myNewObj) // Output: // { // name: 'Caesar', // genre: 'Strategy' // } // Spread 运算符并合并两个对象: const myObjOne = { title: 'Eloquent JavaScript' } const myObjTwo = { author: 'Marijn Haverbeke' } const myNewObj = { ...myObjOne, ...myObjTwo } console.log(myNewObj) // Output: // { // title: 'Eloquent JavaScript', // author: 'Marijn Haverbeke' // }
当从多个对象插入属性并添加新属性时,顺序很重要。
我来解释一下,假设你要用 spread 运算符基于两个现有对象建立一个新对象。第一个已有对象中包含具备某些值的属性 title
。第二个对象也包含属性 title
,可是值不同。最终到底取哪一个 title
?
答案是最后一个。若是对第一个对象使用 spread 运算符,而后再对第二个对象使用,则第二个 title
会生效。若是你将 spread 运算符永在第二个对象上,则第一个 title
会生效。
// Spread 运算符并合并两个对象: const myObjOne = { title: 'Eloquent JavaScript', author: 'Marijn Haverbeke', } const myObjTwo = { title: 'You Don\'t Know JS Yet', language: 'English' } // 用 spread 运算符经过组合 “myObjOne” 和 “myObjTwo” 建立新对象 // 注意:“myObjTwo” 中的 “title” 会将覆盖 “myObjTwo” 的 “title” // 由于“ myObjTwo”排在最后。 const myNewObj = { ...myObjOne, ...myObjTwo } console.log(myNewObj) // Output: // { // title: "You Don't Know JS Yet", // author: 'Marijn Haverbeke', // language: 'English' // } // 注意:“myObjOne” 中的 “title” 将覆盖 “myObjTwo” 的 “title” const myNewObj = { ...myObjTwo, ...myObjOne } console.log(myNewObj) // Output: // { // title: 'Eloquent JavaScript', // language: 'English', // author: 'Marijn Haverbeke' // }
一开始有两个用于 Promise 的回调函数。其中一个是 then()
,在实现诺 Promise 执行。第二个是catch()
,在 promise 被拒绝或 then()
抛出异常时执行。 ES2018 增长了用于 Promise 的第三个回调函数 finally()
。
每次完成 promise 时,都会执行 finally()
回调,无论 promise 是否完成。这个回调的通常用于执行应始终发生的操做。例如关闭模态对话框、关闭数据库链接或进行某些清理。
// finally() example: fetch() .then(response => response.json()) .then(data => console.log(data)) .catch(error => console.log(error)) //最后作点什么: .finally(() => console.log('Operation done.'))
ES2018 还对正则表达式功能进行了的一些改进。这些改进包括 s(dotAll) 标志,后行断言,命名捕获组和 unicode 属性转义。
首先是 s(dotAll) 。与点(.
)不一样,s(dotAll) 容许对换行符及表情符号进行匹配。
// s(dotAll) example: /hello.world/.test('hello\nworld') // Output: // false /hello.world/s.test('hello\nworld') // Output: // true
在ES2018以前,JavaScript仅支持先行断言。先行断言用于基于其后的文原本匹配模式。在 ES2018 中增长了对后行断言的支持。经过它能够基于模式以前的文本模式来进行匹配。后行断言的语法为 ?<=
。
// 后行断言例子: /(?<=green) apple/.test('One red apple is on the table.') // Output: // false /(?<=green) apple/.test('One green apple is on the table.') // Output: // true
断言后面也有一个反向的回溯。仅当子字符串以前没有断言时,此断言才与模式匹配。对后行断言取反操做的语法是 ?<!
。
/(?<!green) apple/.test('One red apple is on the table.') // Output: // true /(?<!green) apple/.test('One green apple is on the table.') // Output: // false
另外一个被 ES2018 引入到正则表达式的好功能是命名捕获组。命名捕获组的语法为 ?<some_name>
。
const date_pattern = /(?<day>\d{2})\/(?<month>\d{2})\/(?<year>\d{4})/ const result = date_pattern.exec('11/12/2021') console.log(result) // Output: // [ // '11/12/2021', // '11', // '12', // '2021', // index: 0, // input: '11/12/2021', // groups: [Object: null prototype] { day: '11', month: '12', year: '2021' } // ] console.log(result.groups.day) // Output: // '11' console.log(result.groups.month) // Output: // '12' console.log(result.groups.year) // Output: // '2021'
每一个 unicode 字符都有许多属性。例如:空白字符,大小写,字母,ASCII,表情符号等。如今你能够在正则表达式中访问这些属性了。
要使用这个功能须要作两件事。首先必须使用 /u
标志。这个标志告诉 JavaScript 你的字符串是一系列 Unicode 代码点。第二是使用 \p{}
。你要检查的属性位于大括号之间,反之则用 \P{}
。
// 用俄语建立一个字符串(西里尔字母): const myStrCyr = 'Доброе утро' //建立英文字符串(拉丁字母): const myStrLat = 'Good morning' //测试“ myStrCyr”是否包含西里尔字符: /\p{Script=Cyrillic}/u.test(myStrCyr) // true //测试“ myStrLat”是否包含西里尔字符: /\p{Script=Cyrillic}/u.test(myStrLat) // false // 测试“myStrLat” 是否包含西里尔字符: /\p{Script=Latin}/u.test(myStrCyr) // false // 测试“myStrLat” 是否包含拉丁语字符: /\p{Script=Latin}/u.test(myStrLat) // true