ES7新特性
ES7在ES6的基礎(chǔ)上添加了三項(xiàng)內(nèi)容:求冪運(yùn)算符()、Array.prototype.includes()**方法、函數(shù)作用域中嚴(yán)格模式的變更。
Array.prototype.includes()方法
includes()的作用,是查找一個(gè)值在不在數(shù)組里,若在,則返回true,反之返回false。 基本用法:
['a', 'b', 'c'].includes('a') // true
['a', 'b', 'c'].includes('d') // false
Array.prototype.includes()方法接收兩個(gè)參數(shù):要搜索的值和搜索的開(kāi)始索引。當(dāng)?shù)诙€(gè)參數(shù)被傳入時(shí),該方法會(huì)從索引處開(kāi)始往后搜索(默認(rèn)索引值為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
那么,我們會(huì)聯(lián)想到ES6里數(shù)組的另一個(gè)方法indexOf,下面的示例代碼是等效的:
['a', 'b', 'c'].includes('a') //true
['a', 'b', 'c'].indexOf('a') > -1 //true
此時(shí),就有必要來(lái)比較下兩者的優(yōu)缺點(diǎn)和使用場(chǎng)景了。
- 簡(jiǎn)便性
從這一點(diǎn)上來(lái)說(shuō),includes略勝一籌。熟悉indexOf的同學(xué)都知道,indexOf返回的是某個(gè)元素在數(shù)組中的下標(biāo)值,若想判斷某個(gè)元素是否在數(shù)組里,我們還需要做額外的處理,即判斷該返回值是否>-1。而includes則不用,它直接返回的便是Boolean型的結(jié)果。
- 精確性
兩者使用的都是 === 操作符來(lái)做值的比較。但是includes()方法有一點(diǎn)不同,兩個(gè)NaN被認(rèn)為是相等的,即使在NaN === NaN結(jié)果是false的情況下。這一點(diǎn)和indexOf()的行為不同,indexOf()嚴(yán)格使用===判斷。請(qǐng)看下面示例代碼:
let demo = [1, NaN, 2, 3]
demo.indexOf(NaN) //-1
demo.includes(NaN) //true
上述代碼中,indexOf()方法返回-1,即使NaN存在于數(shù)組中,而includes()則返回了true。
提示:由于它對(duì)NaN的處理方式與indexOf不同,假如你只想知道某個(gè)值是否在數(shù)組中而并不關(guān)心它的索引位置,建議使用includes()。如果你想獲取一個(gè)值在數(shù)組中的位置,那么你只能使用indexOf方法。
includes()還有一個(gè)怪異的點(diǎn)需要指出,在判斷 +0 與 -0 時(shí),被認(rèn)為是相同的。
[1, +0, 3, 4].includes(-0) //true
[1, +0, 3, 4].indexOf(-0) //1
在這一點(diǎn)上,indexOf()與includes()的處理結(jié)果是一樣的,前者同樣會(huì)返回 +0 的索引值。
注意:在這里,需要注意一點(diǎn),
includes()只能判斷簡(jiǎn)單類型的數(shù)據(jù),對(duì)于復(fù)雜類型的數(shù)據(jù),比如對(duì)象類型的數(shù)組,二維數(shù)組,這些,是無(wú)法判斷的。求冪運(yùn)算符(**)
基本用法
3 ** 2 // 9效果同:
Math.pow(3, 2) // 9** 是一個(gè)用于求冪的中綴算子,比較可知,中綴符號(hào)比函數(shù)符號(hào)更簡(jiǎn)潔,這也使得它更為可取。 下面讓我們擴(kuò)展下思路,既然說(shuō)**是一個(gè)運(yùn)算符,那么它就應(yīng)該能滿足類似加等的操作,我們姑且稱之為冪等,例如下面的例子,a的值依然是9:
let a = 3 a **= 2 // 9對(duì)比下其他語(yǔ)言的指數(shù)運(yùn)算符:
- Python: x ** y
- CoffeeScript: x ** y
- F#: x ** y
- Ruby: x ** y
- Perl: x ** y
- Lua, Basic, MATLAB: x ^ y
不難發(fā)現(xiàn),ES的這個(gè)新特性是從其他語(yǔ)言(Python,Ruby等)模仿而來(lái)的。
ES8新特性
異步函數(shù)(Async functions)
為什么要引入async
眾所周知,JavaScript語(yǔ)言的執(zhí)行環(huán)境是“單線程”的,那么異步編程對(duì)JavaScript語(yǔ)言來(lái)說(shuō)就顯得尤為重要。以前我們大多數(shù)的做法是使用回調(diào)函數(shù)來(lái)實(shí)現(xiàn)JavaScript語(yǔ)言的異步編程?;卣{(diào)函數(shù)本身沒(méi)有問(wèn)題,但如果出現(xiàn)多個(gè)回調(diào)函數(shù)嵌套,例如:進(jìn)入某個(gè)頁(yè)面,需要先登錄,拿到用戶信息之后,調(diào)取用戶商品信息,代碼如下:
this.$http.jsonp('/login', (res) => {
this.$http.jsonp('/getInfo', (info) => { // do something
})
})
假如上面還有更多的請(qǐng)求操作,就會(huì)出現(xiàn)多重嵌套。代碼很快就會(huì)亂成一團(tuán),這種情況就被稱為“回調(diào)函數(shù)地獄”(callback hell)。
于是,我們提出了Promise,它將回調(diào)函數(shù)的嵌套,改成了鏈?zhǔn)秸{(diào)用。寫(xiě)法如下:
var promise = new Promise((resolve, reject) => { this.login(resolve)
})
.then(() => this.getInfo())
.catch(() => { console.log("Error") })
從上面可以看出,Promise的寫(xiě)法只是回調(diào)函數(shù)的改進(jìn),使用then方法,只是讓異步任務(wù)的兩段執(zhí)行更清楚而已。Promise的最大問(wèn)題是代碼冗余,請(qǐng)求任務(wù)多時(shí),一堆的then,也使得原來(lái)的語(yǔ)義變得很不清楚。此時(shí)我們引入了另外一種異步編程的機(jī)制:Generator。
Generator 函數(shù)是一個(gè)普通函數(shù),但是有兩個(gè)特征。一是,function關(guān)鍵字與函數(shù)名之間有一個(gè)星號(hào);二是,函數(shù)體內(nèi)部使用yield表達(dá)式,定義不同的內(nèi)部狀態(tài)(yield在英語(yǔ)里的意思就是“產(chǎn)出”)。一個(gè)簡(jiǎn)單的例子用來(lái)說(shuō)明它的用法:
function* helloWorldGenerator() {
yield 'hello';
yield 'world'; return 'ending';
}
var hw = helloWorldGenerator();
上面代碼定義了一個(gè) Generator 函數(shù)helloWorldGenerator,它內(nèi)部有兩個(gè)yield表達(dá)式(hello和world),即該函數(shù)有三個(gè)狀態(tài):hello,world 和 return 語(yǔ)句(結(jié)束執(zhí)行)。Generator 函數(shù)的調(diào)用方法與普通函數(shù)一樣,也是在函數(shù)名后面加上一對(duì)圓括號(hào)。不同的是,調(diào)用 Generator 函數(shù)后,該函數(shù)并不執(zhí)行,返回的也不是函數(shù)運(yùn)行結(jié)果,而是一個(gè)指向內(nèi)部狀態(tài)的指針對(duì)象,必須調(diào)用遍歷器對(duì)象的next方法,使得指針移向下一個(gè)狀態(tài)。也就是說(shuō),每次調(diào)用next方法,內(nèi)部指針就從函數(shù)頭部或上一次停下來(lái)的地方開(kāi)始執(zhí)行,直到遇到下一個(gè)yield表達(dá)式(或return語(yǔ)句)為止。換言之,Generator 函數(shù)是分段執(zhí)行的,yield表達(dá)式是暫停執(zhí)行的標(biāo)記,而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ù)的機(jī)制更符合我們理解的異步編程思想。
用戶登錄的例子,我們用Generator來(lái)寫(xiě),如下:
var gen = function* () {
const f1 = yield this.login()
const f2 = yield this.getInfo()
};
雖然Generator將異步操作表示得很簡(jiǎn)潔,但是流程管理卻不方便(即何時(shí)執(zhí)行第一階段、何時(shí)執(zhí)行第二階段)。此時(shí),我們便希望能出現(xiàn)一種能自動(dòng)執(zhí)行Generator函數(shù)的方法。我們的主角來(lái)了:async/await。
ES8引入了async函數(shù),使得異步操作變得更加方便。簡(jiǎn)單說(shuō)來(lái),它就是Generator函數(shù)的語(yǔ)法糖。
async function asyncFunc(params) {
const result1 = await this.login()
const result2 = await this.getInfo()
}
是不是更加簡(jiǎn)潔易懂呢?
變體
異步函數(shù)存在以下四種使用形式:
- 函數(shù)聲明:
async function foo() {} - 函數(shù)表達(dá)式:
const foo = async function() {} - 對(duì)象的方式:
let obj = { async foo() {} } - 箭頭函數(shù):
const foo = async () => {}
常見(jiàn)用法匯總
處理單個(gè)異步結(jié)果:
async function asyncFunc() {
const result = await otherAsyncFunc();
console.log(result);
}
順序處理多個(gè)異步結(jié)果:
async function asyncFunc() {
const result1 = await otherAsyncFunc1();
console.log(result1);
const result2 = await otherAsyncFunc2();
console.log(result2);
}
并行處理多個(gè)異步結(jié)果:
async function asyncFunc() {
const [result1, result2] = await Promise.all([
otherAsyncFunc1(),
otherAsyncFunc2()
]);
console.log(result1, result2);
}
處理錯(cuò)誤:
async function asyncFunc() { try {
await otherAsyncFunc();
} catch (err) {
console.error(err);
}
}
若想進(jìn)一步了解async的具體實(shí)踐,可參見(jiàn)阮一峰的博客文章,鏈接奉上:http://es6.ruanyifeng.com/#docs/async
Object.entries()和Object.values()
Object.entries()
如果一個(gè)對(duì)象是具有鍵值對(duì)的數(shù)據(jù)結(jié)構(gòu),則每一個(gè)鍵值對(duì)都將會(huì)編譯成一個(gè)具有兩個(gè)元素的數(shù)組,這些數(shù)組最終會(huì)放到一個(gè)數(shù)組中,返回一個(gè)二維數(shù)組。簡(jiǎn)言之,該方法會(huì)將某個(gè)對(duì)象的可枚舉屬性與值按照二維數(shù)組的方式返回。若目標(biāo)對(duì)象是數(shù)組時(shí),則會(huì)將數(shù)組的下標(biāo)作為鍵值返回。例如:
Object.entries({ one: 1, two: 2 }) //[['one', 1], ['two', 2]]
Object.entries([1, 2]) //[['0', 1], ['1', 2]]
注意:鍵值對(duì)中,如果鍵的值是Symbol,編譯時(shí)將會(huì)被忽略。例如:
Object.entries({ [Symbol()]: 1, two: 2 }) //[['two', 2]]
Object.entries()返回的數(shù)組的順序與for-in循環(huán)保持一致,即如果對(duì)象的key值是數(shù)字,則返回值會(huì)對(duì)key值進(jìn)行排序,返回的是排序后的結(jié)果。例如:
Object.entries({ 3: 'a', 4: 'b', 1: 'c' }) //[['1', 'c'], ['3', 'a'], ['4', 'b']]
使用Object.entries(),我們還可以進(jìn)行對(duì)象屬性的遍歷。例如:
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()很像,顧名思義,它只返回自己的鍵值對(duì)中屬性的值。它返回的數(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ù)通過(guò)填充字符串的首部來(lái)保證字符串達(dá)到固定的長(zhǎng)度,反之,padEnd是填充字符串的尾部來(lái)保證字符串的長(zhǎng)度的。該方法提供了兩個(gè)參數(shù):字符串目標(biāo)長(zhǎng)度和填充字段,其中第二個(gè)參數(shù)可以不填,默認(rèn)情況下使用空格填充。
'Vue'.padStart(10) //' Vue'
'React'.padStart(10) //' React'
'JavaScript'.padStart(10) //'JavaScript'
可以看出,多個(gè)數(shù)據(jù)如果都采用同樣長(zhǎng)度的padStart,相當(dāng)于將呈現(xiàn)內(nèi)容右對(duì)齊。
上面示例中我們只定義了第一個(gè)參數(shù),那么我們現(xiàn)在來(lái)看看第二個(gè)參數(shù),我們可以指定字符串來(lái)代替空字符串。
'Vue'.padStart(10, '_*') //'_*_*_*_Vue'
'React'.padStart(10, 'Hello') //'HelloReact'
'JavaScript'.padStart(10, 'Hi') //'JavaScript'
'JavaScript'.padStart(8, 'Hi') //'JavaScript'</pre>
從上面結(jié)果來(lái)看,填充函數(shù)只有在字符長(zhǎng)度小于目標(biāo)長(zhǎng)度時(shí)才有效,若字符長(zhǎng)度已經(jīng)等于或小于目標(biāo)長(zhǎng)度時(shí),填充字符不會(huì)起作用,而且目標(biāo)長(zhǎng)度如果小于字符串本身長(zhǎng)度時(shí),字符串也不會(huì)做截?cái)嗵幚?,只?huì)原樣輸出。
padEnd函數(shù)作用同padStart,只不過(guò)它是從字符串尾部做填充。來(lái)看個(gè)小例子:
'Vue'.padEnd(10, '_*') //'Vue_*_*_*_'
'React'.padEnd(10, 'Hello') //'ReactHello'
'JavaScript'.padEnd(10, 'Hi') //'JavaScript'
'JavaScript'.padEnd(8, 'Hi') //'JavaScript'</pre>
Object.getOwnPropertyDescriptors()
顧名思義,該方法會(huì)返回目標(biāo)對(duì)象中所有屬性的屬性描述符,該屬性必須是對(duì)象自己定義的,不能是從原型鏈繼承來(lái)的。先來(lái)看個(gè)它的基本用法:
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 }
}
方法還提供了第二個(gè)參數(shù),用來(lái)獲取指定屬性的屬性描述符。
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 }
}
由上述例子可知,該方法返回的描述符,會(huì)有兩種類型:數(shù)據(jù)描述符、存取器描述符。返回結(jié)果中包含的鍵可能的值有:configurable、enumerable、value、writable、get、set。
使用過(guò)Object.assign()的同學(xué)都知道,assign方法只能拷貝一個(gè)屬性的值,而不會(huì)拷貝它背后的復(fù)制方法和取值方法。Object.getOwnPropertyDescriptors()主要是為了解決Object.assign()無(wú)法正確拷貝get屬性和set屬性的問(wèn)題。
let obj = {
id: 1,
name: 'test',
get gender() {
console.log('gender')
}
}
Object.assign(obj) //輸出結(jié)果為:
{
gender: undefined
id: 1,
name: 'test'
}
此時(shí),Object.getOwnPropertyDescriptors方法配合Object.defineProperties方法,就可以實(shí)現(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)绾蝸?lái)拷貝一個(gè)屬性值為賦值方法或者取值方法的對(duì)象。更多Object.getOwnPropertyDescriptors的使用細(xì)則,可參見(jiàn)阮一峰的博客文章,鏈接奉上:http://es6.ruanyifeng.com/#docs/object#Object-getOwnPropertyDescriptors
共享內(nèi)存和原子(Shared memory and atomics)
ES8引入了兩部分內(nèi)容:新的構(gòu)造函數(shù)SharedArrayBuffer、具有輔助函數(shù)的命名空間對(duì)象Atomics。共享內(nèi)存允許多個(gè)線程并發(fā)讀寫(xiě)數(shù)據(jù),而原子操作則能夠進(jìn)行并發(fā)控制,確保多個(gè)存在競(jìng)爭(zhēng)關(guān)系的線程順序執(zhí)行。
共享內(nèi)存和原子也稱為共享陣列緩沖區(qū),它是更高級(jí)的并發(fā)抽象的基本構(gòu)建塊。它允許在多個(gè)工作者和主線程之間共享SharedArrayBuffer對(duì)象的字節(jié)(緩沖區(qū)是共享的,用以訪問(wèn)字節(jié),將其包裝在類型化的數(shù)組中)。這種共享有兩個(gè)好處:
- 可以更快地在web worker之間共享數(shù)據(jù)
- web worker之間的協(xié)調(diào)變得更加簡(jiǎn)單和快速
那么,我們?yōu)槭裁匆牍蚕韮?nèi)存和原子的概念呢?以及SharedArrayBuffer的競(jìng)爭(zhēng)條件是什么,Atomics又是如何解決這種競(jìng)爭(zhēng)的?推薦下面的文章,文章講解很詳細(xì),圖文并茂,帶你深入了解SharedArrayBuffer和Atomics。
內(nèi)存管理碰撞課程:https://segmentfault.com/a/1190000009878588
圖解 ArrayBuffers 和 SharedArrayBuffers:https://segmentfault.com/a/1190000009878632
用 Atomics 避免 SharedArrayBuffers 競(jìng)爭(zhēng)條件:https://segmentfault.com/a/1190000009878699
Atomics對(duì)象提供了許多靜態(tài)方法,配合SharedArrayBuffer對(duì)象一起使用,可以幫助我們?nèi)?gòu)建一個(gè)內(nèi)存共享的多線程編程環(huán)境。Atomic操作安裝在Atomics模塊上。與其他全局對(duì)象不同,Atomics不是構(gòu)造函數(shù)。您不能使用new操作符或Atomics作為函數(shù)調(diào)用該對(duì)象。所有的屬性和方法Atomics都是靜態(tài)的,這一點(diǎn)跟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》一書(shū)中的內(nèi)容。具體章節(jié)鏈接如下:
http://exploringjs.com/es2016-es2017/ch_shared-array-buffer.html
函數(shù)參數(shù)列表與調(diào)用中的尾部逗號(hào)
該特性允許我們?cè)诙x或者調(diào)用函數(shù)時(shí)添加尾部逗號(hào)而不報(bào)錯(cuò)。
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)用是沒(méi)有問(wèn)題的。函數(shù)的這種尾逗號(hào)也是向數(shù)組和字面量對(duì)象中尾逗號(hào)看齊,它適用于那種多行參數(shù)并且參數(shù)名很長(zhǎng)的情況,開(kāi)發(fā)過(guò)程中,如果忘記刪除尾部逗號(hào)也沒(méi)關(guān)系,ES8已經(jīng)支持這種寫(xiě)法。
這么用有什么好處呢?
首先,當(dāng)我們調(diào)整結(jié)構(gòu)時(shí),不會(huì)因?yàn)樽詈笠恍写a的位置變動(dòng),而去添加或者刪除逗號(hào)。
其次,在版本管理上,不會(huì)出現(xiàn)因?yàn)橐粋€(gè)逗號(hào),而使本來(lái)只有一行的修改,變成兩行。例如下面:
從javascript( 'abc' )到javascript( 'abc', 'def' )
在我們版本管理系統(tǒng)里,它會(huì)監(jiān)測(cè)到你有兩處更改,但是如果我們不必去關(guān)心逗號(hào)的存在,每一行都有逗號(hào)時(shí),新加一行,也只會(huì)監(jiān)測(cè)到一行的修改。
建議的ES9功能
回想一下,每個(gè)ECMAScript功能提案都經(jīng)過(guò)了幾個(gè)階段:
- 階段4意味著功能將在下一個(gè)版本中(或之后的版本)。
- 階段3意味著功能仍然有機(jī)會(huì)被包含在下一個(gè)版本中。
第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)標(biāo)志為正則表達(dá)式(Mathias Bynens,Brian Terlson)
- Promise.prototype.finally() (Jordan Harband)
- BigInt - 任意精度整數(shù)(Daniel Ehrenberg)
- Class fields(Daniel Ehrenberg,Jeff Morrison)
- Optional catch binding(Michael Ficarra)