ES7新特性
ES7在ES6的基礎(chǔ)上添加了三項內(nèi)容:求冪運算符()劳秋、Array.prototype.includes()**方法卓练、函數(shù)作用域中嚴格模式的變更。
Array.prototype.includes()方法
includes()
的作用,是查找一個值在不在數(shù)組里恤浪,若在汤求,則返回true
俏险,反之返回false
。 基本用法:
['a', 'b', 'c'].includes('a') // true
['a', 'b', 'c'].includes('d') // false
Array.prototype.includes()方法接收兩個參數(shù):要搜索的值和搜索的開始索引扬绪。當(dāng)?shù)诙€參數(shù)被傳入時竖独,該方法會從索引處開始往后搜索(默認索引值為0)。若搜索值在數(shù)組中存在則返回true
挤牛,否則返回false
莹痢。 且看下面示例:
['a', 'b', 'c', 'd'].includes('b') // true
['a', 'b', 'c', 'd'].includes('b', 1) // true
['a', 'b', 'c', 'd'].includes('b', 2) // false
那么,我們會聯(lián)想到ES6里數(shù)組的另一個方法indexOf墓赴,下面的示例代碼是等效的:
['a', 'b', 'c'].includes('a') //true
['a', 'b', 'c'].indexOf('a') > -1 //true
此時竞膳,就有必要來比較下兩者的優(yōu)缺點和使用場景了。
- 簡便性
從這一點上來說诫硕,includes略勝一籌坦辟。熟悉indexOf的同學(xué)都知道,indexOf返回的是某個元素在數(shù)組中的下標值章办,若想判斷某個元素是否在數(shù)組里锉走,我們還需要做額外的處理,即判斷該返回值是否>-1藕届。而includes則不用挪蹭,它直接返回的便是Boolean型的結(jié)果。
- 精確性
兩者使用的都是 === 操作符來做值的比較休偶。但是includes()方法有一點不同嚣潜,兩個NaN被認為是相等的,即使在NaN === NaN
結(jié)果是false
的情況下椅贱。這一點和indexOf()
的行為不同懂算,indexOf()
嚴格使用===
判斷只冻。請看下面示例代碼:
let demo = [1, NaN, 2, 3]
demo.indexOf(NaN) //-1
demo.includes(NaN) //true
上述代碼中,indexOf()
方法返回-1计技,即使NaN存在于數(shù)組中喜德,而includes()
則返回了true。
提示:由于它對NaN的處理方式與indexOf不同垮媒,假如你只想知道某個值是否在數(shù)組中而并不關(guān)心它的索引位置舍悯,建議使用includes()。如果你想獲取一個值在數(shù)組中的位置睡雇,那么你只能使用indexOf方法萌衬。
includes()
還有一個怪異的點需要指出,在判斷 +0 與 -0 時它抱,被認為是相同的秕豫。
[1, +0, 3, 4].includes(-0) //true
[1, +0, 3, 4].indexOf(-0) //1
在這一點上,indexOf()
與includes()
的處理結(jié)果是一樣的观蓄,前者同樣會返回 +0 的索引值混移。
注意:在這里,需要注意一點侮穿,
includes()
只能判斷簡單類型的數(shù)據(jù)歌径,對于復(fù)雜類型的數(shù)據(jù),比如對象類型的數(shù)組亲茅,二維數(shù)組回铛,這些,是無法判斷的克锣。求冪運算符(**)
基本用法
3 ** 2 // 9
效果同:
Math.pow(3, 2) // 9
** 是一個用于求冪的中綴算子勺届,比較可知,中綴符號比函數(shù)符號更簡潔娶耍,這也使得它更為可取免姿。 下面讓我們擴展下思路,既然說**是一個運算符榕酒,那么它就應(yīng)該能滿足類似加等的操作胚膊,我們姑且稱之為冪等,例如下面的例子想鹰,a的值依然是9:
let a = 3 a **= 2 // 9
對比下其他語言的指數(shù)運算符:
- Python: x ** y
- CoffeeScript: x ** y
- F#: x ** y
- Ruby: x ** y
- Perl: x ** y
- Lua, Basic, MATLAB: x ^ y
不難發(fā)現(xiàn)紊婉,ES的這個新特性是從其他語言(Python,Ruby等)模仿而來的辑舷。
ES8新特性
異步函數(shù)(Async functions)
為什么要引入async
眾所周知喻犁,JavaScript語言的執(zhí)行環(huán)境是“單線程”的,那么異步編程對JavaScript語言來說就顯得尤為重要。以前我們大多數(shù)的做法是使用回調(diào)函數(shù)來實現(xiàn)JavaScript語言的異步編程肢础』顾ǎ回調(diào)函數(shù)本身沒有問題,但如果出現(xiàn)多個回調(diào)函數(shù)嵌套传轰,例如:進入某個頁面剩盒,需要先登錄,拿到用戶信息之后慨蛙,調(diào)取用戶商品信息辽聊,代碼如下:
this.$http.jsonp('/login', (res) => {
this.$http.jsonp('/getInfo', (info) => { // do something
})
})
假如上面還有更多的請求操作,就會出現(xiàn)多重嵌套期贫。代碼很快就會亂成一團跟匆,這種情況就被稱為“回調(diào)函數(shù)地獄”(callback hell)。
于是通砍,我們提出了Promise玛臂,它將回調(diào)函數(shù)的嵌套,改成了鏈式調(diào)用埠帕。寫法如下:
var promise = new Promise((resolve, reject) => { this.login(resolve)
})
.then(() => this.getInfo())
.catch(() => { console.log("Error") })
從上面可以看出,Promise的寫法只是回調(diào)函數(shù)的改進玖绿,使用then方法敛瓷,只是讓異步任務(wù)的兩段執(zhí)行更清楚而已。Promise的最大問題是代碼冗余斑匪,請求任務(wù)多時呐籽,一堆的then,也使得原來的語義變得很不清楚蚀瘸。此時我們引入了另外一種異步編程的機制:Generator狡蝶。
Generator 函數(shù)是一個普通函數(shù),但是有兩個特征贮勃。一是贪惹,function關(guān)鍵字與函數(shù)名之間有一個星號;二是寂嘉,函數(shù)體內(nèi)部使用yield表達式奏瞬,定義不同的內(nèi)部狀態(tài)(yield在英語里的意思就是“產(chǎn)出”)。一個簡單的例子用來說明它的用法:
function* helloWorldGenerator() {
yield 'hello';
yield 'world'; return 'ending';
}
var hw = helloWorldGenerator();
上面代碼定義了一個 Generator 函數(shù)helloWorldGenerator泉孩,它內(nèi)部有兩個yield表達式(hello和world)硼端,即該函數(shù)有三個狀態(tài):hello,world 和 return 語句(結(jié)束執(zhí)行)寓搬。Generator 函數(shù)的調(diào)用方法與普通函數(shù)一樣珍昨,也是在函數(shù)名后面加上一對圓括號。不同的是,調(diào)用 Generator 函數(shù)后镣典,該函數(shù)并不執(zhí)行兔毙,返回的也不是函數(shù)運行結(jié)果,而是一個指向內(nèi)部狀態(tài)的指針對象骆撇,必須調(diào)用遍歷器對象的next方法瞒御,使得指針移向下一個狀態(tài)。也就是說神郊,每次調(diào)用next方法肴裙,內(nèi)部指針就從函數(shù)頭部或上一次停下來的地方開始執(zhí)行,直到遇到下一個yield表達式(或return語句)為止涌乳。換言之蜻懦,Generator 函數(shù)是分段執(zhí)行的,yield表達式是暫停執(zhí)行的標記夕晓,而next方法可以恢復(fù)執(zhí)行宛乃。上述代碼分步執(zhí)行如下:
hw.next() // { value: 'hello', done: false }
hw.next() // { value: 'world', done: false }
hw.next() // { value: 'ending', done: true }
hw.next() // { value: undefined, done: true }
Generator函數(shù)的機制更符合我們理解的異步編程思想。
用戶登錄的例子蒸辆,我們用Generator來寫征炼,如下:
var gen = function* () {
const f1 = yield this.login()
const f2 = yield this.getInfo()
};
雖然Generator將異步操作表示得很簡潔,但是流程管理卻不方便(即何時執(zhí)行第一階段躬贡、何時執(zhí)行第二階段)谆奥。此時,我們便希望能出現(xiàn)一種能自動執(zhí)行Generator函數(shù)的方法拂玻。我們的主角來了:async/await酸些。
ES8引入了async函數(shù),使得異步操作變得更加方便檐蚜。簡單說來魄懂,它就是Generator函數(shù)的語法糖。
async function asyncFunc(params) {
const result1 = await this.login()
const result2 = await this.getInfo()
}
是不是更加簡潔易懂呢闯第?
變體
異步函數(shù)存在以下四種使用形式:
- 函數(shù)聲明:
async function foo() {}
- 函數(shù)表達式:
const foo = async function() {}
- 對象的方式:
let obj = { async foo() {} }
- 箭頭函數(shù):
const foo = async () => {}
常見用法匯總
處理單個異步結(jié)果:
async function asyncFunc() {
const result = await otherAsyncFunc();
console.log(result);
}
順序處理多個異步結(jié)果:
async function asyncFunc() {
const result1 = await otherAsyncFunc1();
console.log(result1);
const result2 = await otherAsyncFunc2();
console.log(result2);
}
并行處理多個異步結(jié)果:
async function asyncFunc() {
const [result1, result2] = await Promise.all([
otherAsyncFunc1(),
otherAsyncFunc2()
]);
console.log(result1, result2);
}
處理錯誤:
async function asyncFunc() { try {
await otherAsyncFunc();
} catch (err) {
console.error(err);
}
}
若想進一步了解async的具體實踐市栗,可參見阮一峰的博客文章,鏈接奉上:http://es6.ruanyifeng.com/#docs/async
Object.entries()和Object.values()
Object.entries()
如果一個對象是具有鍵值對的數(shù)據(jù)結(jié)構(gòu)咳短,則每一個鍵值對都將會編譯成一個具有兩個元素的數(shù)組肃廓,這些數(shù)組最終會放到一個數(shù)組中,返回一個二維數(shù)組诲泌。簡言之盲赊,該方法會將某個對象的可枚舉屬性與值按照二維數(shù)組的方式返回。若目標對象是數(shù)組時敷扫,則會將數(shù)組的下標作為鍵值返回哀蘑。例如:
Object.entries({ one: 1, two: 2 }) //[['one', 1], ['two', 2]]
Object.entries([1, 2]) //[['0', 1], ['1', 2]]
注意:鍵值對中诚卸,如果鍵的值是Symbol,編譯時將會被忽略绘迁。例如:
Object.entries({ [Symbol()]: 1, two: 2 }) //[['two', 2]]
Object.entries()
返回的數(shù)組的順序與for-in循環(huán)保持一致合溺,即如果對象的key值是數(shù)字,則返回值會對key值進行排序缀台,返回的是排序后的結(jié)果棠赛。例如:
Object.entries({ 3: 'a', 4: 'b', 1: 'c' }) //[['1', 'c'], ['3', 'a'], ['4', 'b']]
使用Object.entries()
,我們還可以進行對象屬性的遍歷膛腐。例如:
let obj = { one: 1, two: 2 };
for (let [k,v] of Object.entries(obj)) {
console.log(`${JSON.stringify(k)}: ${JSON.stringify(v)}`);
} //輸出結(jié)果如下:
'one': 1
'two': 2
Object.values()
它的工作原理跟Object.entries()
很像睛约,顧名思義,它只返回自己的鍵值對中屬性的值哲身。它返回的數(shù)組順序辩涝,也跟Object.entries()
保持一致。
Object.values({ one: 1, two: 2 }) //[1, 2]
Object.values({ 3: 'a', 4: 'b', 1: 'c' }) //['c', 'a', 'b']
字符串填充:padStart和padEnd
ES8提供了新的字符串方法-padStart和padEnd勘天。padStart
函數(shù)通過填充字符串的首部來保證字符串達到固定的長度怔揩,反之,padEnd
是填充字符串的尾部來保證字符串的長度的脯丝。該方法提供了兩個參數(shù):字符串目標長度和填充字段商膊,其中第二個參數(shù)可以不填,默認情況下使用空格填充宠进。
'Vue'.padStart(10) //' Vue'
'React'.padStart(10) //' React'
'JavaScript'.padStart(10) //'JavaScript'
可以看出晕拆,多個數(shù)據(jù)如果都采用同樣長度的padStart,相當(dāng)于將呈現(xiàn)內(nèi)容右對齊砰苍。
上面示例中我們只定義了第一個參數(shù)潦匈,那么我們現(xiàn)在來看看第二個參數(shù)阱高,我們可以指定字符串來代替空字符串赚导。
'Vue'.padStart(10, '_*') //'_*_*_*_Vue'
'React'.padStart(10, 'Hello') //'HelloReact'
'JavaScript'.padStart(10, 'Hi') //'JavaScript'
'JavaScript'.padStart(8, 'Hi') //'JavaScript'</pre>
從上面結(jié)果來看,填充函數(shù)只有在字符長度小于目標長度時才有效赤惊,若字符長度已經(jīng)等于或小于目標長度時吼旧,填充字符不會起作用,而且目標長度如果小于字符串本身長度時未舟,字符串也不會做截斷處理圈暗,只會原樣輸出。
padEnd
函數(shù)作用同padStart
裕膀,只不過它是從字符串尾部做填充员串。來看個小例子:
'Vue'.padEnd(10, '_*') //'Vue_*_*_*_'
'React'.padEnd(10, 'Hello') //'ReactHello'
'JavaScript'.padEnd(10, 'Hi') //'JavaScript'
'JavaScript'.padEnd(8, 'Hi') //'JavaScript'</pre>
Object.getOwnPropertyDescriptors()
顧名思義,該方法會返回目標對象中所有屬性的屬性描述符昼扛,該屬性必須是對象自己定義的寸齐,不能是從原型鏈繼承來的。先來看個它的基本用法:
let obj = {
id: 1,
name: 'test',
get gender() {
console.log('gender')
},
set grade(g) {
console.log(g)
}
}
Object.getOwnPropertyDescriptors(obj) //輸出結(jié)果為:
{
gender: {
configurable: true,
enumerable: true,
get: f gender(),
set: undefined
},
grade: {
configurable: true,
enumerable: true,
get: undefined,
set: f grade(g)
},
id: {
configurable: true,
enumerable: true,
value: 1,
writable: true },
name: {
configurable: true,
enumerable: true,
value: 'test',
writable: true }
}
方法還提供了第二個參數(shù),用來獲取指定屬性的屬性描述符惊畏。
let obj = {
id: 1,
name: 'test',
get gender() {
console.log('gender')
},
set grade(g) {
console.log(g)
}
}
Object.getOwnPropertyDescriptors(obj, 'id') //輸出結(jié)果為:
{
id: {
configurable: true,
enumerable: true,
value: 1,
writable: true }
}
由上述例子可知轧飞,該方法返回的描述符,會有兩種類型:數(shù)據(jù)描述符毅厚、存取器描述符塞颁。返回結(jié)果中包含的鍵可能的值有:configurable、enumerable吸耿、value祠锣、writable、get珍语、set锤岸。
使用過Object.assign()
的同學(xué)都知道,assign方法只能拷貝一個屬性的值板乙,而不會拷貝它背后的復(fù)制方法和取值方法是偷。Object.getOwnPropertyDescriptors()
主要是為了解決Object.assign()
無法正確拷貝get
屬性和set
屬性的問題。
let obj = {
id: 1,
name: 'test',
get gender() {
console.log('gender')
}
}
Object.assign(obj) //輸出結(jié)果為:
{
gender: undefined
id: 1,
name: 'test'
}
此時募逞,Object.getOwnPropertyDescriptors
方法配合Object.defineProperties
方法蛋铆,就可以實現(xiàn)正確拷貝。
let obj = {
id: 1,
name: 'test',
get gender() {
console.log('gender')
}
}
let obj1 = {}
Object.defineProperties(obj1, Object.getOwnPropertyDescriptors(obj))
Object.getOwnPropertyDescriptors(obj1) //輸出結(jié)果為:
{
gender: {
configurable: true,
enumerable: true,
get: f gender(),
set: undefined
},
id: {
configurable: true,
enumerable: true,
value: 1,
writable: true
},
name: {
configurable: true,
enumerable: true,
value: 'test',
writable: true
}
}
上述代碼演示了放接,我們?nèi)绾蝸砜截愐粋€屬性值為賦值方法或者取值方法的對象刺啦。更多Object.getOwnPropertyDescriptors
的使用細則,可參見阮一峰的博客文章纠脾,鏈接奉上:http://es6.ruanyifeng.com/#docs/object#Object-getOwnPropertyDescriptors
共享內(nèi)存和原子(Shared memory and atomics)
ES8引入了兩部分內(nèi)容:新的構(gòu)造函數(shù)SharedArrayBuffer
玛瘸、具有輔助函數(shù)的命名空間對象Atomics
。共享內(nèi)存允許多個線程并發(fā)讀寫數(shù)據(jù)苟蹈,而原子操作則能夠進行并發(fā)控制糊渊,確保多個存在競爭關(guān)系的線程順序執(zhí)行。
共享內(nèi)存和原子也稱為共享陣列緩沖區(qū)慧脱,它是更高級的并發(fā)抽象的基本構(gòu)建塊渺绒。它允許在多個工作者和主線程之間共享SharedArrayBuffer
對象的字節(jié)(緩沖區(qū)是共享的,用以訪問字節(jié)菱鸥,將其包裝在類型化的數(shù)組中)宗兼。這種共享有兩個好處:
- 可以更快地在web worker之間共享數(shù)據(jù)
- web worker之間的協(xié)調(diào)變得更加簡單和快速
那么,我們?yōu)槭裁匆牍蚕韮?nèi)存和原子的概念呢氮采?以及SharedArrayBuffer
的競爭條件是什么殷绍,Atomics
又是如何解決這種競爭的?推薦下面的文章鹊漠,文章講解很詳細主到,圖文并茂殖侵,帶你深入了解SharedArrayBuffer
和Atomics
。
內(nèi)存管理碰撞課程:https://segmentfault.com/a/1190000009878588
圖解 ArrayBuffers 和 SharedArrayBuffers:https://segmentfault.com/a/1190000009878632
用 Atomics 避免 SharedArrayBuffers 競爭條件:https://segmentfault.com/a/1190000009878699
Atomics
對象提供了許多靜態(tài)方法镰烧,配合SharedArrayBuffer
對象一起使用拢军,可以幫助我們?nèi)?gòu)建一個內(nèi)存共享的多線程編程環(huán)境。Atomic操作安裝在Atomics
模塊上怔鳖。與其他全局對象不同茉唉,Atomics
不是構(gòu)造函數(shù)。您不能使用new操作符或Atomics
作為函數(shù)調(diào)用該對象结执。所有的屬性和方法Atomics
都是靜態(tài)的度陆,這一點跟Math類似。下面鏈接貼出了Atomics
提供的一些基本方法:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics
關(guān)于共享內(nèi)存和原子的深入研究献幔,也可以參考Axel Rauschmayer博士的《Exploring ES2016 and ES2017》一書中的內(nèi)容懂傀。具體章節(jié)鏈接如下:
http://exploringjs.com/es2016-es2017/ch_shared-array-buffer.html
函數(shù)參數(shù)列表與調(diào)用中的尾部逗號
該特性允許我們在定義或者調(diào)用函數(shù)時添加尾部逗號而不報錯。
let foo = function (a,b,c) {
console.log('a:', a)
console.log('b:', b)
console.log('c:', c)
}
foo(1, 3, 4) //輸出結(jié)果為:
a: 1 b: 3 c: 4
上面這種方式調(diào)用是沒有問題的蜡感。函數(shù)的這種尾逗號也是向數(shù)組和字面量對象中尾逗號看齊蹬蚁,它適用于那種多行參數(shù)并且參數(shù)名很長的情況,開發(fā)過程中郑兴,如果忘記刪除尾部逗號也沒關(guān)系犀斋,ES8已經(jīng)支持這種寫法。
這么用有什么好處呢情连?
首先叽粹,當(dāng)我們調(diào)整結(jié)構(gòu)時,不會因為最后一行代碼的位置變動却舀,而去添加或者刪除逗號虫几。
其次,在版本管理上挽拔,不會出現(xiàn)因為一個逗號辆脸,而使本來只有一行的修改,變成兩行篱昔。例如下面:
從javascript( 'abc' )
到javascript( 'abc', 'def' )
在我們版本管理系統(tǒng)里每强,它會監(jiān)測到你有兩處更改始腾,但是如果我們不必去關(guān)心逗號的存在州刽,每一行都有逗號時,新加一行浪箭,也只會監(jiān)測到一行的修改穗椅。
建議的ES9功能
回想一下,每個ECMAScript功能提案都經(jīng)過了幾個階段:
- 階段4意味著功能將在下一個版本中(或之后的版本)奶栖。
- 階段3意味著功能仍然有機會被包含在下一個版本中匹表。
第4階段和部分ECMAScript規(guī)范草案
以下功能目前在第4階段:
- Template Literal Revision:模板文字修訂(蒂姆·迪士尼)
候選功能(第3階段)
以下功能目前在第3階段:
- Function.prototype.toString 修訂版(Michael Ficarra)
- global(Jordan Harband)
- Rest/Spread Properties:Rest/Spread屬性(SebastianMarkb?ge)
- Asynchronous Iteration:異步迭代(Domenic Denicola)
- import() (Domenic Denicola)
- RegExp Lookbehind Assertions:RegExp Lookbehind斷言(Daniel Ehrenberg)
- RegExp Unicode Property Escapes:RegExp Unicode屬性轉(zhuǎn)義(Brian Terlson门坷,Daniel Ehrenberg,Mathias Bynens)
- RegExp named capture groups:RegExp命名捕獲組(Daniel Ehrenberg袍镀,Brian Terlson)
- s (dotAll) flag for regular expressions:s(dotAll)標志為正則表達式(Mathias Bynens默蚌,Brian Terlson)
- Promise.prototype.finally() (Jordan Harband)
- BigInt - 任意精度整數(shù)(Daniel Ehrenberg)
- Class fields(Daniel Ehrenberg,Jeff Morrison)
- Optional catch binding(Michael Ficarra)