咱们一路奋战,不是为了改变世界,而是为了避免让世界改变咱们。javascript
——《熔炉》java
JavaScript 标准 ECMAScript 的推动是很是活跃的了,自 ES2015 开始,标准制定委员会 TC39 决定每一年为一个周期,推出新版本标准,这无疑为 JavaScript 语言自己注入了强大的活力。git
一个新特性在写入标准前,通常须要通过 5 个阶段:es6
一个提案只要能进入 Stage 2,就差很少确定会包括在之后的正式标准里面。ECMAScript 当前的全部提案,能够在 TC39 的官方网站 GitHub.com/tc39/ecma26… 查看。github
在正式讲解 ES2018 前,先回顾一下 ES2016 和 ES2017 中引入的新特性。正则表达式
includes
方法**
,好比 a ** b
和 Math.pow(a, b)
的效果同样Object.values
,返回由对象属性值组成的数组Object.entries
,返回由一个个由 [key, vlaue]
组成的数组Object.getOwnPropertyDescriptors
,返回一个对象,包含目标对象身上全部的属性描述符(.value
、.writable
、.get
、.set
、.configurable
、.enumerable
)padStart
和 padEnd
方法SharedArrayBuffer
和 Atomics
。好了,如今来看下 ES2018 增长的新特性吧 😍。json
若是一个异步函数中包含一个循环,循环里的每一次迭代是发起一个异步请求。那么怎么保证在本次迭代的请求结束后,再进入下一次迭代呢?🤔数组
你可能想过像下面这样作:浏览器
function process(array) {
array.forEach(async i => {
console.log(`#${i} request start`)
const res = await fetch(`https://jsonplaceholder.typicode.com/todos/${i}`)
const json = await res.json()
console.log(`#${i} request end`, json)
})
}
复制代码
执行后,发现结果并不对:异步
缘由是,数组的 forEach
方法每遍历一次,都是以回调的方式处理当前成员的,每一个迭代之间没有关联,都是各自执行的。所以,请求是按照遍历顺序发出的,然而各个请求有不一样的响应时间,打印顺序跟请求顺序多是不同的。
这与咱们的预期效果——保证在本次迭代的请求结束后,再进入下一次迭代并不符合。
那么换个方式:
async function process(array) {
for (let i of array) {
console.log(`#${i} request start`)
const res = await fetch(`https://jsonplaceholder.typicode.com/todos/${i}`)
const json = await res.json()
console.log(`#${i} request end`, json)
}
}
复制代码
Wow, 这样是能够的~
缘由是,在异步函数中咱们可使用 await
关键字,像书写同步代码同样,书写异步代码。也就是说,等待当前异步代码返回后,再进行后续的逻辑操做。
ES2018 对迭代异步函数进行了加强,引入了异步迭代器。与普通迭代器不一样的是,异步迭代器的 next
方法返回的是一个 Promise。语法是 for..await..of
,咱们将上面的例子换个写法:
async function process(array) {
const reqs = array.map(async id => {
console.log(`#${id} request start`)
const res = await fetch(`https://jsonplaceholder.typicode.com/todos/${id}`)
return res.json()
})
for await (let json of reqs) {
console.log(json)
}
}
复制代码
这种写法老是能保证输出结果和遍历顺序是一致的。
在此特别感谢 @sea_ljf 同窗的指正!
.finally()
方法老是会被执行,不管 Promise 最终状态是 resolve 了,仍是 reject 了。相似于 try...catch...finally
里的 finally
块的做用。
async function process(array) {
fetch('https://jsonplaceholder.typicode.com/todos/1')
.then(response => response.json())
.then(json => console.log(json))
.catch(error => {
console.log(error)
})
.finally(() => {
// 这里的代码老是会被执行
// 不管 Promise 最终状态是 resolve 了,仍是 reject 了
})
}
复制代码
ES2015 引入了三个点(...
)运算符,这个运算符既能用来收集函数的剩余参数,也能够用来扩展数组。
首先来看做为剩余参数运算符的例子:
doSomething(1, 2, 3, 4, 5)
function doSomething(p1, p2, ...p3) {
// p1 的值为 1
// p2 的值为 2
// p3 是一个数组,值为 [3, 4, 5]
}
复制代码
再来看用作扩展运算符的例子:
const values = [99, 100, -1, 48, 16];
console.log( Math.max(...values) ); // 100
复制代码
咱们能够看到,扩展运算符与剩余参数运算符的用法是互为反向的。
ES2015 引入的 ...
运算符,实际上仅适应在对数组的操做上,ES2018 对其进行了加强,将扩展和收集参数的能力扩大到了对象。使得 ...
运算符也能够用来收集对象的“剩余属性”。
一个基础的例子:
const obj = { a: 1, b: 2, c: 3 }
const { a, ...x } = obj
// a 等于 1
// x 的值为 { b: 2, c: 3 }
复制代码
这里的 a
对应的是 obj
的属性 a
;x
则是由 obj
中除 a
属性之外的其余属性组成的对象。
咱们借用这个例子里的 obj
,再来看一个例子:
doSomething(obj)
function doSomething({ a, ...x }) {
// a = 1
// x = { b: 2, c: 3 }
}
复制代码
doSomething
函数接收一个对象参数,调用后,将这个对象拆分红变量 a
和 x
。
咱们也可使用 ...
运算符的扩展功能,将一个对象“扩展”进另外一个对象。
const obj1 = { a: 1, b: 2, c: 3 };
const obj2 = { ...obj1, z: 26 };
// obj2 值变成了 { a: 1, b: 2, c: 3, z: 26 }
复制代码
使用这个特性,咱们还能够实现对象的浅克隆:obj2 = { ...obj1 }
。
在 ES2018 以前,咱们若是要匹配相似 '2018-04-30'
这样的字符串格式,可能会这样作。
const
reDate = /([0-9]{4})-([0-9]{2})-([0-9]{2})/,
match = reDate.exec('2018-04-30'),
year = match[1], // 2018
month = match[2], // 04
day = match[3]; // 30
复制代码
咱们从最终的匹配结果 match
身上,使用索引值,找到对应捕获组匹配的内容。但带来的一个问题是,以后若是匹配格式发生改变,那么 match
对应索引值下内容的含义就不同了。这样咱们势必会修改代码,来提供正确的逻辑。
而 ES2018 容许咱们为捕获组命名,命名方式是在 (
前面使用符号 ?<name>
标识。
const
reDate = /(?<year>[0-9]{4})-(?<month>[0-9]{2})-(?<day>[0-9]{2})/,
match = reDate.exec('2018-04-30'),
year = match.groups.year, // 2018
month = match.groups.month, // 04
day = match.groups.day; // 30
复制代码
全部的命名组的匹配结果,能够在结果对象的 groups
属性中得到。没有命中的捕获组,取值时获得 undefined
。
除此以外,命名捕获组还能够在字符串的 replace
方法中使用:
const
reDate = /(?<year>[0-9]{4})-(?<month>[0-9]{2})-(?<day>[0-9]{2})/,
d = '2018-04-30',
usDate = d.replace(reDate, '$<month>-$<day>-$<year>');
复制代码
这里咱们将 '2019-05-18'
格式化为 '05-18-2019'
。在替换字符串中,使用 $<name>
的形式加入该命名捕获组匹配到的内容。
JavaScript 正则表达式自然支持前行断言。
所谓前言断行,是指 x
只有在 y
前面时才匹配,书写形式如 /x(?=y)/
。好比,下面的例子里,咱们只匹配数字以前的美圆符号:
const
reLookahead = /\$(?=\d+)/,
match = reLookahead.exec('$123');
console.log( match[0] ); // $
复制代码
对应地,所谓后行断断言,是指 x
只有在 y
后面时才匹配,书写形式如 /(?<=y)x/
。一样上面的例子,咱们只匹配美圆符号以后的数字:
const
reLookahead = /(?<=\$)\d+/,
match = reLookahead.exec('$123');
console.log( match[0] ); // 123
复制代码
正则表达式的 .
匹配任意字符,但有两个例外:
0xFFFF
的 Unicode 字符,这些字符每一个占用 4 个字节。\n
)、回车符(\r
)在内的字符。针对第一条限制,咱们可使用 u
修饰符解决;针对第二条限制,咱们则可使用 s
修饰符。
// . 不匹配 \n,因此正则表达式返回 false
/hello.world/.test('hello\nworld') // false
// 这样就能够了
/hello.world/s.test('hello\nworld') // true
复制代码
ES2018 引入了一种新的类的写法 \p{...}
和 \P{...}
,容许正则表达式匹配符合 Unicode 某种属性的全部字符。
const regexGreekSymbol = /\p{Script=Greek}/u;
regexGreekSymbol.test('π') // true
复制代码
上面代码中,\p{Script=Greek}
指定匹配一个希腊文字母,因此匹配 π
成功。
\P{…}
是 \p{…}
的反向匹配,即匹配不知足条件的字符。
注意,这两种类只对 Unicode 有效,因此使用的时候必定要加上
u
修饰符。若是不加u
修饰符,就会报错。
在 JavaScipt 中,字符串中的 \
表示一个转义字符,它提供了 5 种表达字符的方式:
'\z' === 'z' // true(z 无特殊含义,直接输出)
'\172' === 'z' // true(字符的八进制表示)
'\x7A' === 'z' // true(字符的十六进制表示)
'\u007A' === 'z' // true(字符的十六进制表示)
'\u{7A}' === 'z' // true(字符的十六进制表示,支持任意 Unicode 字符码点)
复制代码
这就带来了一个问题,若是字符串中包含 \unicode
或 \xerxes
的话,就会报错,由于会被认为是无效的字符转义。
为了解决这个问题,ES2018 放松了对标签模板里面的字符串转义的限制。若是遇到不合法的字符转义,就返回 undefined
,而不是报错,而且能够从 raw
属性上面能够获得原始字符串。
function tag(strs) {
// strs[0] 等于 undefined
// strs.raw[0] 等于 "\\unicode and \\u{55}";
}
tag`\unicode and \u{55}`
复制代码
感谢你花费宝贵的时间阅读这篇文章。
欢乐的时光老是短暂的,文章到此结束了 🎉。若是你以为个人这篇让你的生活美好了一点点,欢迎鼓(diǎn)励(zàn)😀。若是能在文章下面留下你宝贵的评论或意见是再合适不过的了,由于研究证实,参与讨论比单纯阅读更能让人对知识印象深入😉。
(完)