Javascript更新的速度之快难以跟上脚步,配套的教程却少之又少,今天为大家介绍ES2016 ~ ES2018的新增的功能和特性,并配以详细的代码示例。
下面依照JS版本的顺序开始介绍:
ECMAScript 2016 1. Array.prototype.includes includes是数组的实例方法,这个方法的功能很简单:用于判断某一项是否存在数组里,和这个方法功能类似的有 indexOf,两者的区别是indexOf无法判断 NaN,如图:
1 2 3 4 5 6 7 8 const arr = [1 , 2 , 3 , 4 , NaN ];if (arr.indexOf (3 ) >= 0 ) { console .log (true ) }if (arr.includes (1 )) { console .log (true ) }arr.indexOf (NaN ) arr.includes (NaN )
2. 求幂运算符 求幂运算符: **,用于取代以前的求幂方法 Math.pow,
使用方法如下:
ECMAScript 2017 1. Object.values() Object.values方法和 Object.keys类似,返回类型都是数组,返回的值是对象的值的集合,需要注意一点:两个方法都是返回自身的属性,不包括任何原型链上的属性,如图:
1 2 3 4 5 6 7 const cars = { BMW : 3 , Tesla : 2 , Toyota : 1 }const vals = Object .keys (cars).map (key => cars[key])console .log (vals) const values = Object .values (cars)console .log (values)
2. Object.entries() Object.entries()方法有点像 Object.keys和Object.values的结合体,返回类型是数组,同时数组的每一项也是数组 — 包含两项:key和value,这个方法的好处在于你可以通过for of遍历一次取出key/value ;Object.entries()的返回值(object)还可以直接被转为Map:
例1,遍历:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 const cars = { BMW : 3 , Tesla : 2 , Toyota : 1 }Object .keys (cars).forEach (key => { console .log (`key: ${key} , value: ${cars[key]} ` ) }) Object .entries (carts);for (let [key, value] of Object .entries (cars)) { console .log (`key: ${key} , value: ${cars[key]} ` ) }
例2,把object直接转换为Map:
1 2 3 4 5 6 7 8 9 10 const cars = { BMW : 3 , Tesla : 2 , Toyota : 1 }map1 = new Map () Object .keys (cars).map (key => { map1.set (key, cars[key]) }) console .log (map1) const map2 = new Map (Object .entries (cars))console .log (map2)
3. String padding String增加了两个实例方法 — padStart和 padEnd,这两个方法可以在字符串的首/尾添加其他字符串:
1 2 3 4 'hello' .padStart ('10' , 'a' ) 'hello' .padEnd ('10' , 'b' ) 'hello' .padStart ('7' )
3.1 padStart示例 1 2 3 4 5 6 7 8 9 10 11 const formatted = [0 , 1 , 12 , 123 , 1234 , 12345 ].map (num => num.toString ().padStart (10 , '0' ))console .log (formatted)
3.2 padEnd示例 1 2 3 4 5 6 7 8 const cars = { '🚙BMW' : '10' , '🚘Tesla' : '5' , '🚖Lamborghini' : '0' }Object .entries (cars).map (([name, count] ) => { console .log (`${name.padEnd(20 , ' -' )} Count: ${count.padStart(3 , '0' )} ` ) });
4.Object.getOwnPropertyDescriptors 这个方法的作用是补充Object.assign的功能,在浅拷贝(shallow clone)对象的基础上,也会复制getter和 setter方法:
下面的例子用Object.defineProperties拷贝原对象 Car到新对象ElectricCar来展示 Object.assign和Object.getOwnPropertyDescriptors的不同。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 const Car = { name : 'BMW' , price : 100000 , set discount (x ) { this .d = x }, get discount () { return this .d } }; console .log (Object .getOwnPropertyDescriptor (Car , 'discount' ))const ElectricCar = Object .assign ({}, Car )console .log (Object .getOwnPropertyDescriptor (ElectricCar , 'discount' ))
使用Object.getOwnPropertyDescriptors后:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 const Car = { name : 'BMW' , price : 100000 , set discount (x ) { this .d = x }, get discount () { return this .d } } const ElectricCar2 = Object .defineProperties ({}, Object .getOwnPropertyDescriptors (Car ))
5. 在函数最后一个参数的末尾添加逗号 这是个很小的功能点,在函数形参最后一个参数末尾添加逗号,可以避免git blame提示上一个作者并不存在的改动,代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 function Person (name, age ) { this .name = name; this .age = age }function Person (name, age, gender ) { } function Person (name, age, ) { this .name = name; this .age = age }
6. Async/Await 这个特性是目前为止最重要的一个功能,async函数可以让我们避免频繁调用恶心的 callback,使代码保持干净整洁。
当编译器进入async函数后,遇到 await关键字会暂停执行,可以把await后表达式当作一个 promise,直到promise被resolve或 reject后,函数才会恢复执行,
具体看如下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 function getAmount (userId ) { getUser (userId) .then (getBankBalance) .then (amount => { console .log (amount) }) } async function getAmount2 (userId ) { var user = await getUser (userId) var amount = await getBankBalance () console .log (amount) } getAmount ('1' ) getAmount2 ('1' ) function getUser (userId ) { return new Promise (resolve => { setTimeout (() => { resolve ('张三' ) }, 1000 ) }) } function getBankBalance ( ) { return new Promise ((resolve, reject ) => { setTimeout (() => { if (user === '张三' ) { resolve ('$1,000' ) } else { resolve ('Unknown User' ) } }, 1000 ) }) }
6.1 Async函数本身返回一个Promise 因为async函数返回一个promise,所以想要得到async函数的返回值需要对返回的promise进行then求值。
具体看如下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 async function doubleAndAdd (a, b ) { a = await doubleAfter1Sec (a) b = await doubleAfter1Sec (b) return a + b } doubleAndAdd (1 , 2 ).then (console .log ) async function doubleAfter1Sec (param ) { return new Promise (resolve => { setTimeout (() => { resolve (param * 2 ) }, 1000 ) }) }
6.2 并行调用async/await 上一个函数doubleAndAdd里依次调用了两个 async函数,但是每次调用都必须等待1秒,性能很差;因为参数a和参数 b之间并无耦合,所以我们可以使用Promise.all来并行执行这两次调用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 async function doubleAndAdd (a, b ) { const [a, b] = Promise .all ([doubleAfter1Sec (a), doubleAfter1Sec (b)]) return a + b } doubleAndAdd (1 , 2 ).then (console .log ) async function doubleAfter1Sec (param ) { return new Promise (resolve => { setTimeout (() => { resolve (param * 2 ) }, 1000 ) }) }
6.3 async/await的错误处理 async/await对错误处理有很多方法:
1. 在函数内使用try/catch 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 async function doubleAndAdd (a, b ) { try { a = await doubleAfter1Sec (a) b = await doubleAfter1Sec (b) } catch (e) { return NaN } return a + b } doubleAndAdd ('one' , 2 ).then (console .log ) doubleAndAdd (1 , 2 ).then (console .log ) async function doubleAfter1Sec (param ) { return new Promise (resolve => { setTimeout (() => { const val = param * 2 isNaN (val) ? reject (NaN ) : resolve (val) }, 1000 ) }) }
2. catch 所有await表达式 因为await表达式返回一个 promise,所以我们可以在await表达式后直接执行 catch来处理错误
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 async function doubleAndAdd (a, b ) { a = await doubleAfter1Sec (a).catch (e => console .log (`'a' is NaN` )) b = await doubleAfter1Sec (b).catch (e => console .log (`'b' is NaN` )) if (!a || !b) return NaN return a + b } doubleAndAdd ('one' , 2 ).then (console .log ) doubleAndAdd (1 , 2 ).then (console .log ) async function doubleAfter1Sec (param ) { return new Promise (resolve => { setTimeout (() => { const val = param * 2 isNaN (val) ? reject (NaN ) : resolve (val) }, 1000 ) }) }
3. catch 整个async-await函数 1 2 3 4 5 6 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 )
ECMAScript 2018 ECMAScript目前在最终稿阶段,将会在2018年6月或7月正式推出。下面介绍的所有特性属于stage-4,即将成为ECMAScript 2018的一部分。
1. 共享内存和原子性 这是JS的一个高级特性,也是JS引擎的核心改进。
共享内存的主要思想是: 把多线程的特性带到JS,为了提高代码的性能和高并发,由之前的JS引擎管理内存变为自己管理内存。
这个特性由一个新的全局对象SharedArrayBuffer来实现,这个对象在一块共享内存区储存数据,JS的主线程和web-worker线程共享这部分数据。
当前,如果我们想要在JS主线程和web-worker线程间共享数据时,必须使用postMessage在不同线程间传递数据,有了 SharedArrayBuffer后,不同的线程可以直接访问这个对象来共享数据。
但是多线程间的共享内存会产生竞态条件,为了避免这种情况,JS引入了原子性的全局对象。这个对象提供了多种方法来保证正在被某个线程访问的内存被锁住,以达到内存安全。
2. Tagged Template literal(带标签的模板字面量?) 限制被移除 首先弄懂一个概念:什么s是Tagged Template literal ?
tagged template literal出现在es2015以后,允许开发者自定义字符串被嵌入的值。举一个例子,标准的字符串嵌入一个值的方式是:
1 const userName = '张三' const greetings = `hello ${userName} !` console .log (greetings)
在tagged template literal里,你可以用一个函数通过参数来接收字符串写死的各部分,比如: [‘hello’, ‘!’]和之后被替换为值的变量[‘张三’],最后通过函数返回任何你想要的结果,这个函数被称作Tagged函数,下面 Tagged函数greet来扩展上例中的greetings:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 const userName = '张三' const greetings = `hello ${userName} !` console .log (greetings)function greet (hardCodedPartsArray, ...replacementPartsArray ) { let str = '' hardCodedPartsArray.forEach ((part, i ) => { if (i < replacementPartsArray.length ) { str += `${part} ${replacementPartsArray[i] || '' } ` } else { str += `${part} ${timeGreet()} ` } }) return str } function timeGreet ( ) { const hr = new Date ().getHours () return hr < 12 ? '早上好!' : hr < 18 ? '下午好!' : '晚上好!' }
3. 正则表达式中的.匹配所有字符 在目前的正则表达式中,虽然.点被认为代表所以字符,实际上它不会匹配像 \n、\r和 \f等换行符。
例如:
1 2 /first.second /.test ('first\nsecond' );
这个改进使.点操作符匹配任意单个字符。为了保证下面这段代码在任何JS版本都正常工作,我们在结尾加上 /s修饰符
1 2 /first.second /s.test ('first\nsecond' );
4. 正则表达式捕获Named Group 这个改进带来了在其他语言中比如:Java、Python等已经支持了的有用的正则特性。这个特性允许开发者在正则中为不同的组写格式为(<?name>)的名字标识符,之后可以在匹配的结果里通过名字标识的组来获取对应的值。
4.1 基础示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 const re1 = /(\d{4})-(\d{2})-(\d{2})/ const result1 = re1.exec ('2015-01-08' )console .log (result1)const re2 = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u const result2 = re2.exec ('2015-01-08' )console .log (result2)
4.2 在正则自身使用命名组 我们可以使用格式\k<group name>
在正则自身来引用之前的组。下面的用例子展示:
1 2 3 4 5 6 7 8 const sameWords = /(?<fruit>apple|orange)==\k<fruit>/u sameWords.test ('apple==apple' ) sameWords.test ('orange==orange' ) sameWords.test ('apple==orange' )
4.3 在String.prototype.replace里使用命名组 命名组特性已经被添加到replace方法里,所以我们可以轻松地替换字符串了。
例如: 改变”firstName, lastName” 为 “lastName, firstName”:
1 2 const re = /(?<firstName>[A-Za-z]+) (?<lastName>[A-Za-z]+)/u 'John Lennon' .replace (re, '$<lastName>, $<firstName>' )
5. 对象的Rest properties Rest操作符…(三个点)允许我们取出剩余的对象属性
5.1 使用Rest properties来取出你想要使用的属性 1 2 3 4 5 6 7 8 let { name, age, ...remaining } = { name : '张三' , age : 20 , gender : '男' , address : 'xxxxx' } name age
5.2 你甚至可以移除不想要的属性 1 2 3 4 5 6 7 let {address, ...cleanObj} = { name : 'john' , address : '北京市海淀区' , gender : '男' }cleanObj
6. 对象的Spread properties Spread属性看起来和Rest属性很像,也是三个点…操作符,不同的是Spread用于创建新对象。
1 2 3 4 const person = {name : 'john' , age : 20 }const address = {city : 'Beijing' , country : 'china' }const personWithAddress = { ...person, ...address}personWithAddress
7. 正则Lookbehind断言 这个正则的改进允许我们保证在一些字符串之前存在某些字符串。
你可以使用一组(?<=…)(问号,小于等于)寻找后面肯定的断言。
更进一步,你可以使用(?<!…(问号,小于号叹号)寻找后面否定的断言。
肯定断言: 比如我们想要确定在符号#出现在单词 winning前,即#winning,只返回 winning:
1 2 3 4 5 6 /(?<=#).*/.test('winning') / / false /(?<=#).*/.test('#winning') / / true '#winning' .match (/#.*/ )[0 ] '#winning' .match (/(?<=#).*/ )[0 ]
否定断言:比如我们想要取出数字前标志是#,而不是$的数字
1 2 'this is a test signal $1.23' .match (/(?<!\$)\d+\.\d+/ ) 'this is a test signal #2.43' .match (/(?<!\$)\d+\.\d+/ )[0 ]
8. 正则Unicode属性转义符 用正则匹配所有的unicode字符很困难。像\w、 \W、\d等只能匹配英文字符和数字,但是出现在其他语言比如希腊语里的数字我们要怎么处理呢?
Unicode属性转义符就是为了解决这个问题。它使Unicode为每个字符添加描述性的metadata。
例如: Unicode数据库把所有北印度语字符归在一个值为Devanagari的属性 Script和另一个值也为的Devanagari的属性 Script_Extensions的组下,所以我们可以通过搜索Script_Extensions来得到所有北印度语字符。
Starting in ECMAScript 2018, we can use \p to escape characters along with {Script=Devanagari} to match all those Indian characters. That is, we can use: \p{Script=Devanagari} in the RegEx to match all Devanagari characters.
从ES2018开始,我们可以使用\p配合{Script=Devanagari}的转义字符来匹配所有北印度语字符,也就是用转义字符 \p{Script=Devanagari}来匹配所有Devanagari字符。
1 2 3 /^p{Script =Devanagari }+$/u.test ('हिन्दी' )
相似的,Unicode把所有希腊语字符用属性Script_Extensions (和Script)值为 Greek来分组,所以我们可以用Script_Extensions=Greek或 Script=Greek来搜索所有希腊语字符。
也就是说,我们可以用转义字符\p{Script=Greek}来匹配所有希腊语字符:
1 /\p{Script _Extensions=Greek }/u.test ('π' )
更多的,Unicode数据库储存了很多种类型的Emoji字符,以Boolean属性Emoji、 Emoji_Component、Emoji_Presentation、 Emoji_Modifier和Emoji_Modifier_Base,值为 true来分组,我们可以通过使用Emoji来搜索所有Emoji字符。
也就是通过转义字符\p{Emoji}来匹配各种Emoji字符
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 /\p{Emoji }/u.test ('❤️' ) /\p{Emoji }\p{Emoji _Modifier}/u.test ('✌️' ); /\p{Emoji}\p{Emoji_Modifier}/u .test ('✌🏽' ); /\p{Emoji}\p{Emoji_Modifier}/u .test ('✌🏽' );
Lastly, we can use capital “P”(\P ) escape character instead of small p (\p ), to negate the matches.
最后,我们可以使用大写P(\P)转义字符来匹配和小写\p匹配内容相反的内容。
8. Promise.prototype.finally() finally()是新加到Promise上的实例方法。主要用处是在 resolve或reject回调函数执行完后,执行清理任务。
finally回调函数不携带任何参数,不管任何情况下都会被执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 let started = true let myPromise = new Promise ((resolve, reject ) => { resolve ('all good' ) }).then (val => { console .log (val) }).catch (e => { console .log (e) }).finally (() => { console .log ('这个函数总是会被执行' ) started = false }) let started = true let myPromise = new Promise ((resolve, reject ) => { reject ('reject apple' ) }).then (val => { console .log (val) }).catch (e => { console .log (e) }).finally (() => { console .log ('这个函数总是会被执行' ) started = false }) let started = trueletmyPromise = new Promise ((resolve, reject ) => { throw new Error ('error' ) }).then (val => { console .log (val) }).catch (e => { console .log (e) }).finally (() => { console .log ('这个函数总是会被执行' ) started = false }) let started = trueletmyPromise = new Promise ((resolve, reject ) => { throw new Error ('something happened' ) }).then (val => { console .log (val) }).catch (e => { throw new Error ('throw another error' ) }).finally (() => { console .log ('这个函数总是会被执行' ) started = false })
9. 异步循环 这是一个特别有用的特性。根本上讲,它允许我们轻易地在异步函数里创建循环。
这个特性添加了一个新的for-await-of
循环,允许我们在一个循环里调用返回promise(或者是每一项为promise的数组)的异步函数。
最cool的地方是这个循环会等待每个 promise resolve 后再去执行下一次循环。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 const promises = [ new Promise (resolve => resolve (1 )), new Promise (resolve => resolve (2 )), new Promise (resolve => resolve (3 )), ] async function test1 ( ) { for (const obj of promises) { console .log (obj) } } async function test2 ( ) { for await (const obj of promises) { console .log (obj) } } test1 () test2 ()