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