最近我寫了一篇博客文章,甚至做一篇關(guān)于ES6/ES2015在線課程。你猜怎么樣?TC39-JavaScript最強(qiáng)勢的監(jiān)工-正在邁向ES8,所以讓我們來了解下ES7 and ES8(官方稱ES2016 and ES2017),幸運(yùn)的是,他們比ES6標(biāo)準(zhǔn)功能少好多好多,真的,ES7只有2個新特性。
ES7 特性:
1.Array.prototype.includes
2.Exponentiation Operator(求冪運(yùn)算)

Paste_Image.png
ES8在本文(2017年1月)之前尚未完成。但我們可以假設(shè)所有完成的提案(第4階段)和大多數(shù)階段3(更多的階段在這里和我的課程)2017年(ES8)完成的提案:
1.Object.values/Object.entries
2.String padding(字符串填充)
3.Object.getOwnPropertyDescriptors
4.函數(shù)參數(shù)列表和調(diào)用中的尾逗號(Trailing commas)
5.異步函數(shù)(Async Functions)
本文中我不會介紹stage 3的提案,但你可以在這里檢查階段1到3的建議的狀態(tài)。
讓我們深入了解建議和功能。
Array.prototype.includes
Array.prototype.includes用法都容易和簡單。它是一個替代indexOf,開發(fā)人員用來檢查數(shù)組中是否存在值,indexOf是一種尷尬的使用,因?yàn)樗祷匾粋€元素在數(shù)組中的位置或者-1當(dāng)這樣的元素不能被找到的情況下。所以它返回一個數(shù)字,而不是一個布爾值。開發(fā)人員需要實(shí)施額外的檢查。在ES6,要檢查是否存在值你需要做一些如下圖所示小技巧,因?yàn)樗麄儧]有匹配到值,Array.prototype.indexOf返回-1變成了true(轉(zhuǎn)換成true),但是當(dāng)匹配的元素為0位置時候,該數(shù)組包含元素,卻變成了false
letarr = ['react','angular','vue']// WRONGif(arr.indexOf('react')) {// 0 -> evaluates to false, definitely as we expectedconsole.log('Can use React')// this line would never be executed}// Correctif(arr.indexOf('react') !==-1) {console.log('Can use React')}
或者使用一點(diǎn)點(diǎn)hack 位運(yùn)算符~使代碼更加緊湊一些,因?yàn)閪(位異或)對任何數(shù)字相當(dāng)于-(a + 1):
letarr = ['react','angular','vue']// Correctif(~arr.indexOf('react')) {console.log('Can use React')}
在ES7中使用includes代碼如下:
letarr = ['react','angular','vue']// Correctif(arr.includes('react')) {console.log('Can use React')}
開發(fā)者還能在字符串中使用includes:
letstr ='React Quickly'// Correctif(str.toLowerCase().includes('react')) {// trueconsole.log('Found "react"')? }
有趣的是,許多JavaScript庫已經(jīng)實(shí)現(xiàn)includes或類似功能contains
(但TC39決定不使用名稱contains因?yàn)镸ooTools):
jQuery:$.inArray
Underscore.js:_.contains
Lodash:_.includes(在版本3或者早期版本中是_.contains和 Underscore一樣)
CoffeeScript:in操作(example)
Darf:list.contains(example)
除了增強(qiáng)了可讀性語義化,實(shí)際上給開發(fā)者返回布爾值,而不是匹配的位置。includes也可以在NaN(非數(shù)字)使用。最后 ,includes第二可選參數(shù)fromIndex,這對于優(yōu)化是有好處的,因?yàn)樗试S從特定位置開始尋找匹配。
更多例子:
console.log([1,2,3].includes(2))// === true)console.log([1,2,3].includes(4))// === false)console.log([1,2,NaN].includes(NaN))// === true)console.log([1,2,-0].includes(+0))// === true)console.log([1,2, +0].includes(-0))// === true)console.log(['a','b','c'].includes('a'))// === true)console.log(['a','b','c'].includes('a',1))// === false)
總而言之,includes在一個數(shù)組或者列表中檢查是否存在一個值,給任何開發(fā)人員帶來簡單性。
Exponentiation Operator(求冪運(yùn)算)
求冪運(yùn)算大多數(shù)是為開發(fā)者做一些數(shù)學(xué)計(jì)算,對于3D,VR,SVG還有數(shù)據(jù)可視化非常有用。在ES6或者早些版本,你不得不創(chuàng)建一個循環(huán),創(chuàng)建一個遞歸函數(shù)或者使用Math.pow,如果你忘記了什么是指數(shù),當(dāng)你有相同數(shù)字(基數(shù))自相相乘多次(指數(shù))。例如,7的3次方是7*7*7
所以在ES6/2015ES,你能使用Math.pow創(chuàng)建一個短的遞歸箭頭函數(shù)
calculateExponent =(base, exponent) =>base*((--exponent>1)?calculateExponent(base, exponent):base)console.log(calculateExponent(7,12) ===Math.pow(7,12))// trueconsole.log(calculateExponent(2,7) ===Math.pow(2,7))// true
現(xiàn)在在ES7 /ES2016,以數(shù)學(xué)向?qū)У拈_發(fā)者可以使用更短的語法:
leta =7**12letb =2**7console.log(a ===Math.pow(7,12))// trueconsole.log(b ===Math.pow(2,7))// true
開發(fā)者還可以操作結(jié)果:
leta =7a **=12letb =2b **=7console.log(a ===Math.pow(7,12))// trueconsole.log(b ===Math.pow(2,7))// true
許多ES新特性是從其他語言(CoffeeScript-俺最愛,Ruby等)模仿而來的。你可以猜到,指數(shù)運(yùn)算符在其他語言的存在形式:
Python:x ** y
CoffeeScript:x ** y
F#:x ** y
Ruby:x ** y
Perl:x ** y
Lua, Basic, MATLAB:x ^ y
對我個人而言沒有指數(shù)運(yùn)算不是什么問題。除了這次,在我15年的Javascript訪談和代碼教程寫作中沒有寫過指數(shù)運(yùn)算。指數(shù)運(yùn)算符是你們最需要的特性么?
Object.values/Object.entries
Object.values和Object.entries是在ES2017規(guī)格中,它和Object.keys類似,返回?cái)?shù)組類型,其序號和Object.keys序號對應(yīng)。
Object.values,Object.entries和Object.keys各自項(xiàng)返回是數(shù)組,相對應(yīng)包括key,value或者可枚舉特定對象property/attribute
在ES8 /ES2017之前,Javascript開發(fā)者需要迭代一個對象的自身屬性時候不得不用Object.keys,通過迭代且使用obj[key]獲取value值返回一個數(shù)組:
letobj = {a:1,b:2,c:3}Object.keys(obj).forEach((key, index)=>{console.log(key, obj[key])})
而使用ES6/ES2015 中for/of稍微好點(diǎn):
letobj = {a:1,b:2,c:3}for(letkeyofObject.keys(obj)) {console.log(key, obj[key])}
你使用老方式for/in(ES5)也許用的非常好。但是他會迭代所有可以枚舉屬性(像原型中的帶名字的-seeMDN),不僅僅自己的屬性,會意外的破壞那些 像prototype和tostring得到意想不到的值。
Object.values返回對象自身可以迭代屬性值(values)為數(shù)組類型。我們最好使用Array.prototype.forEach迭代它,結(jié)合ES6的箭頭函數(shù)隱形返回值:
letobj = {a:1,b:2,c:3}Object.values(obj).forEach(value=>console.log(value))// 1, 2, 3
或者使用for/of:
letobj = {a:1,b:2,c:3}for(letvalueofObject.values(obj)) {console.log(value)}// 1, 2, 3
·Object.entries·,在另一方面,將會返回對象自身可迭代屬性key-value對數(shù)組(作為一個數(shù)組),他們(key-value)分別以數(shù)組存放數(shù)組中。
letobj = {a:1,b:2,c:3}JSON.stringify(Object.entries(obj))"[["a",1],["b",2],["c",3]]"
我們可以使用ES6/ES2015解構(gòu)(需要深入了解解構(gòu)請點(diǎn)擊這篇文章和課程),從這嵌套數(shù)組中分別聲明key和value
letobj = {a:1,b:2,c:3}Object.entries(obj).forEach(([key, value]) =>{console.log(`${key}is${value}`)})// a is 1, b is 2, c is 3
你可以猜一猜,我們同樣使用ES6for/of(畢竟全部都是數(shù)組)遍歷Object.entries返回來的結(jié)果值。
letobj = {a:1,b:2,c:3}for(let[key, value]ofObject.entries(obj)) {console.log(`${key}is${value}`)}// a is 1, b is 2, c is 3
現(xiàn)在從對象中提取values和key-value pairs 變得非常容易了。Object.values和Object.entries這種方式不想之前Object.keys(自身屬性key+順序相同)結(jié)合for/of(ES6)一起,我們不僅僅可以提取他們還可以迭代他們。
字符填充函數(shù)padStart 和 padEnd
String.prototype.padStart和String.prototype.padEnd在javascript字符操作是一個不錯的體驗(yàn),幫助避免依賴而外的庫。
padStart()在開始部位填充,返回一個給出長度的字符串,填充物給定字符串,把字符串填充到期望的長度。從字符串的左邊開始(至少大部分西方語言),一個經(jīng)典例子是使用空格創(chuàng)建列:
console.log('react'.padStart(10).length)// "? ? ? react" is 10console.log('backbone'.padStart(10).length)// "? backbone" is 10
它對于財(cái)務(wù)方面非常有用:
console.log('0.00'.padStart(20))console.log('10,000.00'.padStart(20))console.log('250,000.00'.padStart(20))
這結(jié)果作為一個會計(jì)總賬格式非常漂亮:
0.0010,000.00250,000.00
第二個參數(shù),讓我們放一些其他的填充字符替代空字符串,一個字符串填充:
console.log('react'.padStart(10,'_'))// "_____react"console.log('backbone'.padStart(10,'*'))// "**backbone"
padEnd顧名思義就是從字符串的尾端右邊開始填充。第二個參數(shù),你能實(shí)際上用一個任何長度的字符串。例如:
console.log('react'.padEnd(10,':-)'))// "react:-):-" is 10console.log('backbone'.padEnd(10,'*'))// "backbone**" is 10
Object.getOwnPropertyDescriptors
這新的Object.getOwnPropertyDescriptors返回對象obj所有自身屬性描述。這是一個多參數(shù)版本的Object.getOwnPropertyDescriptors(obj,propName)將會返回obj中propName屬性的一個單獨(dú)描述。
在我們?nèi)粘2豢勺兙幊蹋╥mmutable programming)時代中,有了這個方法很方便(記住,Javascript中對象是引用傳遞)在ES5中,開發(fā)者要使用Object.assign()來拷貝對象,Object.assign()分配屬性只有copy和定義新的屬性。當(dāng)我們使用更加復(fù)雜對象和類原型,這可能會出問題。
Object.getOwnPropertyDescriptors允許創(chuàng)建真實(shí)的對象淺副本并創(chuàng)建子類,它通過給開發(fā)者描述符來做到這一點(diǎn).在Object.create(prototype, object)放入描述符后,返回一個真正的淺拷貝
Object.create(Object.getPrototypeOf(obj),Object.getOwnPropertyDescriptors(obj))
或者你可以合并兩個對象target和source如下:
Object.defineProperties(? target,Object.getOwnPropertyDescriptors(source))
以上是Object.getOwnPropertyDesciptors用法。但是什么是描述符(descriptor)呢?就是一個對象的描述,廢話!
好吧!好吧!,讓我們挖掘一下描述符一點(diǎn)點(diǎn)多信息。這里有兩種描述符號類型:
1.數(shù)據(jù)描述符(Data descriptor)
2.存取器描述符(Accessor descriptor)
存取描述符有必須屬性:get 或者set或者get和set兩個就是如你所想的getter和setter函數(shù),然后存取描述符還有可選屬性configurable和enumerable
letazatsBooks = {books: ['React Quickly'],? get latest () {letnumberOfBooks =this.books.lengthif(numberOfBooks ==0)returnundefinedreturnthis.books[numberOfBooks -1]? }}
這個例子數(shù)據(jù)描述符books由Object.getOwnPropertyDescriptor(azatsBooks, 'books')產(chǎn)生結(jié)果如下:
Objectconfigurable:trueenumerable:truevalue:Array[1]? ? writable:true__proto__:Object
同樣的,Object.getOwnPropertyDescriptor(azatsBooks, 'latest')將會展現(xiàn)latest的描述符,這個latest(get)存取器描述符展現(xiàn)如下:
Objectconfigurable: truee? ? numerable:trueget: latest()? ? set:undefined__proto__:Object
現(xiàn)在我們調(diào)用新方法獲取所有的描述符:
console.log(Object.getOwnPropertyDescriptors(azatsBooks))
它會給出這個對象兩個描述符books和latest:
Objectbooks:Objectconfigurable:trueenumerable:truevalue:Array[1]? ? writable:true__proto__:Objectlatest:Objectconfigurable:trueenumerable:trueget: latest()? ? set:undefined__proto__:Object__proto__:Object
或者你可以使用Devtools格式化一下,下面截圖:

Paste_Image.png
函數(shù)參數(shù)列表和調(diào)用中的尾逗號
尾逗號在函數(shù)定義中只是一個純粹語法變化,在ES5中,將會非法語法,在函數(shù)參數(shù)后面應(yīng)該是沒有逗號的:
varf =function(a,
? b,
? c,
? d){// NO COMMA!// ...console.log(d)}f(1,2,3,'this')
在ES8中,這種尾逗號是沒有問題的:
varf =function(a,
? b,
? c,
? d,){// COMMA? OK!// ...console.log(d)}f(1,2,3,'this')
現(xiàn)在,函數(shù)中尾逗號是向數(shù)組(ES3)中和字面量對象(ES5)中尾逗號看齊。
vararr = [1,// Length == 32,3,]// <--- okletobj = {a:1,// Only 3 propertiesb:2,c:3,}// <--- ok
更不用說他是無用友好的。
尾逗號主要有用在使用多行參數(shù)風(fēng)格(典型的是那些很長的參數(shù)名),開發(fā)者終于可以忘記逗號放在第一位這種奇怪的寫法。自從逗號bugs主要原因就是使用他們。而現(xiàn)在你可以到處使用逗號,甚至最后參數(shù)都可以。
異步函數(shù)
異步函數(shù)(或者async/await)特性操作是Promise最重要的功能。所以你大概進(jìn)一步閱讀他們或者看一個進(jìn)修視頻課程來。這種想法是為了在寫異步代碼中簡化它,因?yàn)槿祟惔竽X最討厭這種平行非序號思維了。它只是不會演變這種方式。
對于我個人來說,我不喜歡Promise,就僅僅相比callback顯得特別冗余。所以我從來沒有使用過它,幸運(yùn)的是,在ES8,異步函數(shù)是那么給力。開發(fā)者定義一個asyc函數(shù)里面不包含或者包含await 基于Promise異步操作。在這引擎之下一個異步函數(shù)返回一個Promise,無論無何你在任何地方不會看到這樣的一個詞(注:Promise)(當(dāng)然了,你非的自己使用)。
例如,在ES6中我們可以使用Promise,Axios庫向GraphQL服務(wù)器發(fā)送一個請求:
axios.get(`/q?query=${query}`)? .then(response=>response.data)? .then(data=>{this.props.processfetchedData(data)// Defined somewhere else})? .catch(error=>console.log(error))
任何一個Promise庫都能兼容新的異步函數(shù),我們可以使用同步try/catch做錯誤處理。
asyncfetchData(url) => {try{constresponse =awaitaxios.get(`/q?query=${query}`)constdata = response.datathis.props.processfetchedData(data)? }catch(error) {console.log(error)? }}
異步函數(shù)返回一個Promise,所以我們像下面可以繼續(xù)執(zhí)行流程:
asyncfetchData(query) => {try{constresponse =awaitaxios.get(`/q?query=${query}`)constdata = response.datareturndata? }catch(error) {console.log(error)? }}fetchData(query).then(data=>{this.props.processfetchedData(data)})
你可以看到這段代碼在(Babel REPL)生效。請注意,這個例子中,Axios庫被代替的,是通過模擬來做相同功能,而HTTP請求通過setTimout代替:
letaxios = {// mocksget:function(x){returnnewPromise(resolve=>{? ? setTimeout(()=>{? ? ? resolve({data: x})? ? },2000)? })}}letquery ='mangos'asyncfunctionfetchData(query){try{constresponse =awaitaxios.get(`/q?query=${query}`)constdata = response.datareturndata? }catch(error) {console.log(error)? }}fetchData(query).then(data=>{console.log(data)// Got data 2s later... Can use data!})
有了 async/await,我們的代碼執(zhí)行異步看起來像執(zhí)行同步一樣。可以從頭到尾讀起來非常簡單和易懂,因?yàn)槌霈F(xiàn)結(jié)果順序和函數(shù)題中從頭到尾順序一樣啊!
小結(jié)
這就是或多或少ES8特性(還沒有定稿),和已經(jīng)定稿的ES7(已經(jīng)定稿),如果你使用Babel,Traceur或者類似編譯器,您可以使用這些所有功能以及更多0~3 stage功能,而不需要等瀏覽器實(shí)現(xiàn)他們。ES7和ES8將簡單轉(zhuǎn)換ES5兼容代碼,即使是IE9都可以運(yùn)行:)
一些ES8功能需要注意了,因?yàn)樗麄冞€是處于stage3階段,但是有可能出現(xiàn)ES8/ES2017中。