最近我寫了一篇博客文章,甚至做一篇關于ES6/ES2015在線課程。你猜怎么樣?TC39-JavaScript最強勢的監工-正在邁向ES8,所以讓我們來了解下ES7 and ES8(官方稱ES2016 and ES2017),幸運的是,他們比ES6標準功能少好多好多,真的,ES7只有2個新特性。
ES7 特性:
1.Array.prototype.includes
2.Exponentiation Operator(求冪運算)
ES8在本文(2017年1月)之前尚未完成。但我們可以假設所有完成的提案(第4階段)和大多數階段3(更多的階段在這里和我的課程)2017年(ES8)完成的提案:
1.Object.values
/Object.entries
2.String padding(字符串填充)
3.Object.getOwnPropertyDescriptors
4.函數參數列表和調用中的尾逗號(Trailing commas)
5.異步函數(Async Functions)
本文中我不會介紹stage 3的提案,但你可以在這里檢查階段1到3的建議的狀態。
讓我們深入了解建議和功能。
Array.prototype.includes
Array.prototype.includes用法都容易和簡單。它是一個替代indexOf,開發人員用來檢查數組中是否存在值,indexOf是一種尷尬的使用,因為它返回一個元素在數組中的位置或者-1當這樣的元素不能被找到的情況下。所以它返回一個數字,而不是一個布爾值。開發人員需要實施額外的檢查。在ES6,要檢查是否存在值你需要做一些如下圖所示小技巧,因為他們沒有匹配到值,Array.prototype.indexOf返回-1變成了true(轉換成true),但是當匹配的元素為0位置時候,該數組包含元素,卻變成了false
let arr = ['react', 'angular', 'vue']
// WRONG
if (arr.indexOf('react')) { // 0 -> evaluates to false, definitely as we expected
console.log('Can use React') // this line would never be executed
}
// Correct
if (arr.indexOf('react') !== -1) {
console.log('Can use React')
}
或者使用一點點hack 位運算符 ~
使代碼更加緊湊一些,因為~
(位異或)對任何數字相當于-(a + 1)
:
let arr = ['react', 'angular', 'vue']
// Correct
if (~arr.indexOf('react')) {
console.log('Can use React')
}
在ES7中使用includes
代碼如下:
let arr = ['react', 'angular', 'vue']
// Correct
if (arr.includes('react')) {
console.log('Can use React')
}
開發者還能在字符串中使用includes
:
let str = 'React Quickly'
// Correct
if (str.toLowerCase().includes('react')) { // true
console.log('Found "react"')
}
有趣的是,許多JavaScript庫已經實現includes
或類似功能contains
(但TC39決定不使用名稱contains
因為MooTools):
- jQuery: $.inArray
- Underscore.js: _.contains
- Lodash: _.includes (在版本3或者早期版本中是
_.contains
和 Underscore一樣) - CoffeeScript:
in
操作(example) - Darf:
list.contains
(example)
除了增強了可讀性語義化,實際上給開發者返回布爾值,而不是匹配的位置。includes
也可以在NaN
(非數字)使用。最后 ,includes
第二可選參數fromIndex
,這對于優化是有好處的,因為它允許從特定位置開始尋找匹配。
更多例子:
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在一個數組或者列表中檢查是否存在一個值,給任何開發人員帶來簡單性。
Exponentiation Operator(求冪運算)
求冪運算大多數是為開發者做一些數學計算,對于3D,VR,SVG還有數據可視化非常有用。在ES6或者早些版本,你不得不創建一個循環,創建一個遞歸函數或者使用Math.pow
,如果你忘記了什么是指數,當你有相同數字(基數)自相相乘多次(指數)。例如,7的3次方是7*7*7
所以在ES6/2015ES,你能使用Math.pow
創建一個短的遞歸箭頭函數
calculateExponent = (base, exponent) => base*((--exponent>1)?calculateExponent(base, exponent):base)
console.log(calculateExponent(7,12) === Math.pow(7,12)) // true
console.log(calculateExponent(2,7) === Math.pow(2,7)) // true
現在在ES7 /ES2016,以數學向導的開發者可以使用更短的語法:
let a = 7 ** 12
let b = 2 ** 7
console.log(a === Math.pow(7,12)) // true
console.log(b === Math.pow(2,7)) // true
開發者還可以操作結果:
let a = 7
a **= 12
let b = 2
b **= 7
console.log(a === Math.pow(7,12)) // true
console.log(b === Math.pow(2,7)) // true
許多ES新特性是從其他語言(CoffeeScript-俺最愛,Ruby等)模仿而來的。你可以猜到,指數運算符在其他語言的存在形式:
- Python:
x ** y
- CoffeeScript:
x ** y
- F#:
x ** y
- Ruby:
x ** y
- Perl:
x ** y
- Lua, Basic, MATLAB:
x ^ y
對我個人而言沒有指數運算不是什么問題。除了這次,在我15年的Javascript訪談和代碼教程寫作中沒有寫過指數運算。指數運算符是你們最需要的特性么?
Object.values/Object.entries
Object.values
和 Object.entries
是在ES2017規格中,它和Object.keys
類似,返回數組類型,其序號和Object.keys
序號對應。
Object.values
,Object.entries
和Object.keys
各自項返回是數組,相對應包括key,value或者可枚舉特定對象property/attribute
在ES8 /ES2017之前,Javascript開發者需要迭代一個對象的自身屬性時候不得不用Object.keys
,通過迭代且使用obj[key]
獲取value值返回一個數組:
let obj = {a: 1, b: 2, c: 3}
Object.keys(obj).forEach((key, index)=>{
console.log(key, obj[key])
})
而使用ES6/ES2015 中for/of
稍微好點:
let obj = {a: 1, b: 2, c: 3}
for (let key of Object.keys(obj)) {
console.log(key, obj[key])
}
你使用老方式for/in
(ES5)也許用的非常好。但是他會迭代所有可以枚舉屬性(像原型中的帶名字的-see MDN),不僅僅自己的屬性,會意外的破壞那些 像prototype
和tostring
得到意想不到的值。
Object.values
返回對象自身可以迭代屬性值(values)為數組類型。我們最好使用Array.prototype.forEach
迭代它,結合ES6的箭頭函數隱形返回值:
let obj = {a: 1, b: 2, c: 3}
Object.values(obj).forEach(value=>console.log(value)) // 1, 2, 3
或者使用for/of
:
let obj = {a: 1, b: 2, c: 3}
for (let value of Object.values(obj)) {
console.log(value)
}
// 1, 2, 3
·Object.entries·,在另一方面,將會返回對象自身可迭代屬性key-value對數組(作為一個數組),他們(key-value)分別以數組存放數組中。
let obj = {a: 1, b: 2, c: 3}
JSON.stringify(Object.entries(obj))
"[["a",1],["b",2],["c",3]]"
我們可以使用ES6/ES2015解構(需要深入了解解構請點擊這篇文章和課程),從這嵌套數組中分別聲明key和value
let obj = {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
(畢竟全部都是數組)遍歷Object.entries
返回來的結果值。
let obj = {a: 1, b: 2, c: 3}
for (let [key, value] of Object.entries(obj)) {
console.log(`${key} is ${value}`)
}
// a is 1, b is 2, c is 3
現在從對象中提取values和key-value pairs 變得非常容易了。Object.values
和Object.entries
這種方式不想之前 Object.keys
(自身屬性key+順序相同)結合for/of
(ES6)一起,我們不僅僅可以提取他們還可以迭代他們。
字符填充函數padStart 和 padEnd
String.prototype.padStart
和 String.prototype.padEnd
在javascript字符操作是一個不錯的體驗,幫助避免依賴而外的庫。
padStart()
在開始部位填充,返回一個給出長度的字符串,填充物給定字符串,把字符串填充到期望的長度。從字符串的左邊開始(至少大部分西方語言),一個經典例子是使用空格創建列:
console.log('react'.padStart(10).length) // " react" is 10
console.log('backbone'.padStart(10).length) // " backbone" is 10
它對于財務方面非常有用:
console.log('0.00'.padStart(20))
console.log('10,000.00'.padStart(20))
console.log('250,000.00'.padStart(20))
這結果作為一個會計總賬格式非常漂亮:
0.00
10,000.00
250,000.00
第二個參數,讓我們放一些其他的填充字符替代空字符串,一個字符串填充:
console.log('react'.padStart(10, '_')) // "_____react"
console.log('backbone'.padStart(10, '*')) // "**backbone"
padEnd
顧名思義就是從字符串的尾端右邊開始填充。第二個參數,你能實際上用一個任何長度的字符串。例如:
console.log('react'.padEnd(10, ':-)')) // "react:-):-" is 10
console.log('backbone'.padEnd(10, '*')) // "backbone**" is 10
Object.getOwnPropertyDescriptors
這新的 Object.getOwnPropertyDescriptors
返回對象obj所有自身屬性描述。這是一個多參數版本的Object.getOwnPropertyDescriptors(obj,propName)將會返回obj中propName屬性的一個單獨描述。
在我們日常不可變編程(immutable programming)時代中,有了這個方法很方便(記住,Javascript中對象是引用傳遞)在ES5中,開發者要使用Object.assign()
來拷貝對象, Object.assign()
分配屬性只有copy和定義新的屬性。當我們使用更加復雜對象和類原型,這可能會出問題。
Object.getOwnPropertyDescriptors
允許創建真實的對象淺副本并創建子類,它通過給開發者描述符來做到這一點.在Object.create(prototype, object)
放入描述符后,返回一個真正的淺拷貝
Object.create(
Object.getPrototypeOf(obj),
Object.getOwnPropertyDescriptors(obj)
)
或者你可以合并兩個對象target
和source
如下:
Object.defineProperties(
target,
Object.getOwnPropertyDescriptors(source)
)
以上是Object.getOwnPropertyDesciptors
用法。但是什么是描述符(descriptor)呢?就是一個對象的描述,廢話!
好吧!好吧!,讓我們挖掘一下描述符一點點多信息。這里有兩種描述符號類型:
1.數據描述符(Data descriptor)
2.存取器描述符(Accessor descriptor)
存取描述符有必須屬性:get 或者set或者get和set兩個就是如你所想的getter和setter函數,然后存取描述符還有可選屬性configurable
和enumerable
let azatsBooks = {
books: ['React Quickly'],
get latest () {
let numberOfBooks = this.books.length
if (numberOfBooks == 0) return undefined
return this.books[numberOfBooks - 1]
}
}
這個例子數據描述符books
由Object.getOwnPropertyDescriptor(azatsBooks, 'books')
產生結果如下:
Object
configurable: true
enumerable: true
value: Array[1]
writable: true
__proto__: Object
同樣的,Object.getOwnPropertyDescriptor(azatsBooks, 'latest')
將會展現latest的描述符,這個latest(get)存取器描述符展現如下:
Object
configurable: truee
numerable: true
get: latest()
set: undefined
__proto__: Object
現在我們調用新方法獲取所有的描述符:
console.log(Object.getOwnPropertyDescriptors(azatsBooks))
它會給出這個對象兩個描述符books和latest:
Object
books: Object
configurable: true
enumerable: true
value: Array[1]
writable: true
__proto__: Object
latest: Object
configurable: true
enumerable: true
get: latest()
set: undefined
__proto__: Object
__proto__: Object
或者你可以使用Devtools格式化一下,下面截圖:
函數參數列表和調用中的尾逗號
尾逗號在函數定義中只是一個純粹語法變化,在ES5中,將會非法語法,在函數參數后面應該是沒有逗號的:
var f = function(a,
b,
c,
d) { // NO COMMA!
// ...
console.log(d)
}
f(1,2,3,'this')
在ES8中,這種尾逗號是沒有問題的:
var f = function(a,
b,
c,
d,
) { // COMMA? OK!
// ...
console.log(d)
}
f(1,2,3,'this')
現在,函數中尾逗號是向數組(ES3)中和字面量對象(ES5)中尾逗號看齊。
var arr = [1, // Length == 3
2,
3,
] // <--- ok
let obj = {a: 1, // Only 3 properties
b: 2,
c: 3,
} // <--- ok
更不用說他是無用友好的。
尾逗號主要有用在使用多行參數風格(典型的是那些很長的參數名),開發者終于可以忘記逗號放在第一位這種奇怪的寫法。自從逗號bugs主要原因就是使用他們。而現在你可以到處使用逗號,甚至最后參數都可以。
異步函數
異步函數(或者async/await)特性操作是Promise最重要的功能。所以你大概進一步閱讀他們或者看一個進修視頻課程來。這種想法是為了在寫異步代碼中簡化它,因為人類大腦最討厭這種平行非序號思維了。它只是不會演變這種方式。
對于我個人來說,我不喜歡Promise,就僅僅相比callback顯得特別冗余。所以我從來沒有使用過它,幸運的是,在ES8,異步函數是那么給力。開發者定義一個asyc
函數里面不包含或者包含await 基于Promise異步操作。在這引擎之下一個異步函數返回一個Promise,無論無何你在任何地方不會看到這樣的一個詞(注:Promise)(當然了,你非的自己使用)。
例如,在ES6中我們可以使用Promise,Axios庫向GraphQL服務器發送一個請求:
axios.get(`/q?query=${query}`)
.then(response => response.data)
.then(data => {
this.props.processfetchedData(data) // Defined somewhere else
})
.catch(error => console.log(error))
任何一個Promise庫都能兼容新的異步函數,我們可以使用同步try/catch做錯誤處理。
async fetchData(url) => {
try {
const response = await axios.get(`/q?query=${query}`)
const data = response.data
this.props.processfetchedData(data)
} catch (error) {
console.log(error)
}
}
異步函數返回一個Promise,所以我們像下面可以繼續執行流程:
async fetchData(query) => {
try {
const response = await axios.get(`/q?query=${query}`)
const data = response.data
return data
} catch (error) {
console.log(error)
}
}
fetchData(query).then(data => {
this.props.processfetchedData(data)
})
你可以看到這段代碼在(Babel REPL)生效。請注意,這個例子中,Axios庫被代替的,是通過模擬來做相同功能,而HTTP請求通過setTimout代替:
let axios = { // mocks
get: function(x) {
return new Promise(resolve => {
setTimeout(() => {
resolve({data: x})
}, 2000)
})
}}
let query = 'mangos'
async function fetchData(query) {
try {
const response = await axios.get(`/q?query=${query}`)
const data = response.data
return data
} catch (error) {
console.log(error)
}
}
fetchData(query).then(data => {
console.log(data) // Got data 2s later... Can use data!
})
有了 async/await,我們的代碼執行異步看起來像執行同步一樣。可以從頭到尾讀起來非常簡單和易懂,因為出現結果順序和函數題中從頭到尾順序一樣啊!
小結
這就是或多或少ES8特性(還沒有定稿),和已經定稿的ES7(已經定稿),如果你使用Babel,Traceur或者類似編譯器,您可以使用這些所有功能以及更多0~3 stage功能,而不需要等瀏覽器實現他們。ES7和ES8將簡單轉換ES5兼容代碼,即使是IE9都可以運行:)
一些ES8功能需要注意了,因為他們還是處于stage3階段,但是有可能出現ES8/ES2017中。
- 共享內存和原子(Shared Memory and Atomics)
- SIMD.JS - SIMD APIs
- Function.prototype.toString
- 提升模板文字限制(Lifting Template Literal Restriction)
- global
- Rest/Spread Properties
- 異步迭代(Asynchronous Iteration)
- import()
你可以時刻關注他們的狀態 進行中提案和已經完成的提案
PS:需要復習 Primose、箭頭函數、let/const和其他ES6/ES2015功能,請閱讀我的博客和視頻。
PS2:如果你更喜歡觀看視頻的話,請登錄Node.University’sES6/ES2015 course,還有即將來臨的ES7和ES8視頻課程
原文地址:https://node.university/blog/498412/es7-es8?utm_source=javascriptweekly&utm_medium=email 【愛在西元前翻譯】