ES2018 规范引入了四个新功能。这些功能包括异步迭代,rest/spread 属性,Promise.prototype.finally()
和正则表达式改进。本问将帮你了解这些 ES2018 功能的工作原理及使用方法。
异步迭代
异步迭代是讨论的比较少 ES2018 功能之一。虽然还有很多关于 ES2018 其他功能的讨论,但几乎没有关于异步迭代这方面的内容。通过异步迭代,我们可以得到异步的可迭代对象和迭代器。
这意味着你可以把 await
关键字与 for…of
循环放在一起使用。你可以用这些循环对可迭代对象进行迭代。可迭代对象的包括数组、map、set,NodeList,函数的 arguments 参数,TypedArray 等。
在 ES2018 之前,for...of
循环是同步的。如果你试着迭代涉及异步操作的可迭代对象并 await
,则无法正常工作。循环本身会保持同步,基本上忽略 await
,并在其内部的异步操作可以完成之前完成迭代。
// 下面的代码在 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...)。
// 创建一个异步函数:
async function processResponses(someIterable) {
//遍历可迭代对象并等待异步操作的结果
for await (let item of someIterable) {
processItem(item)
}
}
Rest/Spread 属性
rest
和 spread
并不是真正的新功能。两者都是在 ES6 中作为新的运算符引入的,它们很快就开始流行起来。可以说 JavaScript 程序员喜欢它们。唯一的问题是它们只能用在数组和参数上,不过 ES2018 把这两个功能引入了对象中。
rest
和 spread
运算符的语法都非常简单,由三个点(...
)组成。这些点后面是要在其上使用 rest
或 spread
运算符的对象。接下来简单的讨论一下两者的工作原理。
对象的 rest 运算符
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
运算符,需要记住两点:首先,只能用一次,除非把它用在嵌套对象上。其次,必须在最后使用。这就是为什么在上面的例子中,在解构前两个属性之后而不是之前看到它的原因。
// 这行代码不起作用,因为把 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 运算符还允许你从多个对象插入属性。也可以把这个运算符与添加新属性结合使用。
// 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.prototype.finally()
一开始有两个用于 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) 允许对换行符及表情符号进行匹配。
// 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 属性转义
每个 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
本文首发微信公众号:前端先锋
欢迎扫描二维码关注公众号,每天都给你推送新鲜的前端技术文章
欢迎继续阅读本专栏其它高赞文章:
- 深入理解Shadow DOM v1
- 一步步教你用 WebVR 实现虚拟现实游戏
- 13个帮你提高开发效率的现代CSS框架
- 快速上手BootstrapVue
- JavaScript引擎是如何工作的?从调用栈到Promise你需要知道的一切
- WebSocket实战:在 Node 和 React 之间进行实时通信
- 关于 Git 的 20 个面试题
- 深入解析 Node.js 的 console.log
- Node.js 究竟是什么?
- 30分钟用Node.js构建一个API服务器
- Javascript的对象拷贝
- 程序员30岁前月薪达不到30K,该何去何从
- 14个最好的 JavaScript 数据可视化库
- 8 个给前端的顶级 VS Code 扩展插件
- Node.js 多线程完全指南
- 把HTML转成PDF的4个方案及实现
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。