es7,es8

ES7新特性

ES7在ES6的基礎上添加了三項內容:求冪運算符(**)、Array.prototype.includes()方法、函數做用域中嚴格模式的變動。javascript

Array.prototype.includes()方法

includes()的做用,是查找一個值在不在數組裏,若在,則返回 true,反之返回 false。 基本用法:php

  1. ['a', 'b', 'c'].includes('a')     // true css

  2. ['a', 'b', 'c'].includes('d')     // falsehtml

Array.prototype.includes()方法接收兩個參數:要搜索的值和搜索的開始索引。當第二個參數被傳入時,該方法會從索引處開始日後搜索(默認索引值爲0)。若搜索值在數組中存在則返回 true,不然返回 false。 且看下面示例:java

  1. ['a', 'b', 'c', 'd'].includes('b')         // truepython

  2. ['a', 'b', 'c', 'd'].includes('b', 1)      // truegit

  3. ['a', 'b', 'c', 'd'].includes('b', 2)      // falsees6

那麼,咱們會聯想到ES6裏數組的另外一個方法indexOf,下面的示例代碼是等效的:github

  1. ['a', 'b', 'c'].includes('a')          //true web

  2. ['a', 'b', 'c'].indexOf('a') > -1      //true

此時,就有必要來比較下二者的優缺點和使用場景了。

  • 簡便性

從這一點上來講,includes略勝一籌。熟悉indexOf的同窗都知道,indexOf返回的是某個元素在數組中的下標值,若想判斷某個元素是否在數組裏,咱們還須要作額外的處理,即判斷該返回值是否>-1。而includes則不用,它直接返回的即是Boolean型的結果。

  • 精確性

二者使用的都是 === 操做符來作值的比較。可是includes()方法有一點不一樣,兩個NaN被認爲是相等的,即便在 NaN===NaN結果是 false的狀況下。這一點和 indexOf()的行爲不一樣, indexOf()嚴格使用 ===判斷。請看下面示例代碼:

  1. et demo = [1, NaN, 2, 3]

  2. demo.indexOf(NaN)        //-1

  3. demo.includes(NaN)       //true

上述代碼中, indexOf()方法返回-1,即便NaN存在於數組中,而 includes()則返回了true。

提示:因爲它對NaN的處理方式與indexOf不一樣,假如你只想知道某個值是否在數組中而並不關心它的索引位置,建議使用includes()。若是你想獲取一個值在數組中的位置,那麼你只能使用indexOf方法。

  includes()還有一個怪異的點須要指出,在判斷 +0 與 -0 時,被認爲是相同的。

  1. [1, +0, 3, 4].includes(-0)    //true

  2. [1, +0, 3, 4].indexOf(-0)     //1

在這一點上, indexOf()與 includes()的處理結果是同樣的,前者一樣會返回 +0 的索引值。

注意:在這裏,須要注意一點, includes()只能判斷簡單類型的數據,對於複雜類型的數據,好比對象類型的數組,二維數組,這些,是沒法判斷的。

求冪運算符(**)

基本用法

3 ** 2           // 9

效果同:

Math.pow(3, 2)   // 9

 

* 是一個用於求冪的中綴算子,比較可知,中綴符號比函數符號更簡潔,這也使得它更爲可取。 下面讓咱們擴展下思路,既然說*是一個運算符,那麼它就應該能知足相似加等的操做,咱們姑且稱之爲冪等,例以下面的例子,a的值依然是9:

  1. let a = 3

  2. a **= 2

  3. // 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語言的異步編程。回調函數自己沒有問題,但若是出現多個回調函數嵌套,例如:進入某個頁面,須要先登陸,拿到用戶信息以後,調取用戶商品信息,代碼以下:

  1. this.$http.jsonp('/login', (res) => {

  2.  this.$http.jsonp('/getInfo', (info) => {

  3.    // do something

  4.  })

  5. })

假如上面還有更多的請求操做,就會出現多重嵌套。代碼很快就會亂成一團,這種狀況就被稱爲「回調函數地獄」(callback hell)。

因而,咱們提出了Promise,它將回調函數的嵌套,改爲了鏈式調用。寫法以下:

  1. var promise = new Promise((resolve, reject) => {

  2.  this.login(resolve)

  3. })

  4. .then(() => this.getInfo())

  5. .catch(() => { console.log("Error") })

從上面能夠看出,Promise的寫法只是回調函數的改進,使用then方法,只是讓異步任務的兩段執行更清楚而已。Promise的最大問題是代碼冗餘,請求任務多時,一堆的then,也使得原來的語義變得很不清楚。此時咱們引入了另一種異步編程的機制:Generator。

Generator 函數是一個普通函數,可是有兩個特徵。一是,function關鍵字與函數名之間有一個星號;二是,函數體內部使用yield表達式,定義不一樣的內部狀態(yield在英語裏的意思就是「產出」)。一個簡單的例子用來講明它的用法:

  1. function* helloWorldGenerator() {

  2.  yield 'hello';

  3.  yield 'world'; 

  4.  return 'ending';

  5. }

  6. var hw = helloWorldGenerator();

上面代碼定義了一個 Generator 函數helloWorldGenerator,它內部有兩個yield表達式(hello和world),即該函數有三個狀態:hello,world 和 return 語句(結束執行)。Generator 函數的調用方法與普通函數同樣,也是在函數名後面加上一對圓括號。不一樣的是,調用 Generator 函數後,該函數並不執行,返回的也不是函數運行結果,而是一個指向內部狀態的指針對象,必須調用遍歷器對象的next方法,使得指針移向下一個狀態。也就是說,每次調用next方法,內部指針就從函數頭部或上一次停下來的地方開始執行,直到遇到下一個yield表達式(或return語句)爲止。換言之,Generator 函數是分段執行的,yield表達式是暫停執行的標記,而next方法能夠恢復執行。上述代碼分步執行以下: 

  1. // { value: 'hello', done: false }

  2. hw.next()

  3. // { value: 'world', done: false }

  4. hw.next()

  5. // { value: 'ending', done: true }

  6. hw.next() 

  7. // { value: undefined, done: true }

Generator函數的機制更符合咱們理解的異步編程思想。

用戶登陸的例子,咱們用Generator來寫,以下:

  1. var gen = function* () {

  2.  const f1 = yield this.login() 

  3.  const f2 = yield this.getInfo()

  4. };

雖然Generator將異步操做表示得很簡潔,可是流程管理卻不方便(即什麼時候執行第一階段、什麼時候執行第二階段)。此時,咱們便但願能出現一種能自動執行Generator函數的方法。咱們的主角來了:async/await。

ES8引入了async函數,使得異步操做變得更加方便。簡單說來,它就是Generator函數的語法糖。

  1. async function asyncFunc(params) {

  2.  const result1 = await this.login()

  3.  const result2 = await this.getInfo() 

  4. }

是否是更加簡潔易懂呢?

變體

異步函數存在如下四種使用形式:

  • 函數聲明: asyncfunctionfoo(){}

  • 函數表達式: constfoo=asyncfunction(){}

  • 對象的方式: letobj={asyncfoo(){}}

  • 箭頭函數: constfoo=async()=>{}

常見用法彙總

處理單個異步結果:

  1. async function asyncFunc() {

  2.  const result = await otherAsyncFunc();

  3.  console.log(result);

  4. }

順序處理多個異步結果:

  1. async function asyncFunc() {

  2.  const result1 = await otherAsyncFunc1();

  3.  console.log(result1);

  4.  const result2 = await otherAsyncFunc2();

  5.  console.log(result2);

  6. }

並行處理多個異步結果:

  1. async function asyncFunc() {

  2.  const [result1, result2] = await Promise.all([

  3.    otherAsyncFunc1(),

  4.    otherAsyncFunc2()

  5.  ]);

  6.  console.log(result1, result2);

  7. }

處理錯誤:

  1. async function asyncFunc() {

  2.  try {

  3.    await otherAsyncFunc();

  4.  } catch (err) {

  5.    console.error(err);

  6.  }

  7. }

若想進一步瞭解async的具體實踐,可參見阮一峯的博客文章,連接奉上:http://es6.ruanyifeng.com/#docs/async

Object.entries()和Object.values()

Object.entries()

若是一個對象是具備鍵值對的數據結構,則每個鍵值對都將會編譯成一個具備兩個元素的數組,這些數組最終會放到一個數組中,返回一個二維數組。簡言之,該方法會將某個對象的可枚舉屬性與值按照二維數組的方式返回。若目標對象是數組時,則會將數組的下標做爲鍵值返回。例如:

  1. Object.entries({ one: 1, two: 2 })    //[['one', 1], ['two', 2]]

  2. Object.entries([1, 2])                //[['0', 1], ['1', 2]]

注意:鍵值對中,若是鍵的值是Symbol,編譯時將會被忽略。例如:

  1. Object.entries({ [Symbol()]: 1, two: 2 })       //[['two', 2]]

Object.entries()返回的數組的順序與for-in循環保持一致,即若是對象的key值是數字,則返回值會對key值進行排序,返回的是排序後的結果。例如:

  1. Object.entries({ 3: 'a', 4: 'b', 1: 'c' })    //[['1', 'c'], ['3', 'a'], ['4', 'b']]

使用 Object.entries(),咱們還能夠進行對象屬性的遍歷。例如:

  1. let obj = { one: 1, two: 2 };

  2. for (let [k,v] of Object.entries(obj)) {

  3.  console.log(`${JSON.stringify(k)}: ${JSON.stringify(v)}`);

  4. }

  5.  

  6. //輸出結果以下:

  7. 'one': 1

  8. 'two': 2

Object.values()

它的工做原理跟 Object.entries()很像,顧名思義,它只返回本身的鍵值對中屬性的值。它返回的數組順序,也跟 Object.entries()保持一致。

  1. Object.values({ one: 1, two: 2 })            //[1, 2]

  2. Object.values({ 3: 'a', 4: 'b', 1: 'c' })    //['c', 'a', 'b']

字符串填充:padStart和padEnd

ES8提供了新的字符串方法-padStart和padEnd。 padStart函數經過填充字符串的首部來保證字符串達到固定的長度,反之, padEnd是填充字符串的尾部來保證字符串的長度的。該方法提供了兩個參數:字符串目標長度和填充字段,其中第二個參數能夠不填,默認狀況下使用空格填充。

  1. 'Vue'.padStart(10)           //'       Vue'

  2. 'React'.padStart(10)         //'     React'

  3. 'JavaScript'.padStart(10)    //'JavaScript'

能夠看出,多個數據若是都採用一樣長度的padStart,至關於將呈現內容右對齊。

上面示例中咱們只定義了第一個參數,那麼咱們如今來看看第二個參數,咱們能夠指定字符串來代替空字符串。

  1. 'Vue'.padStart(10, '_*')           //'_*_*_*_Vue'

  2. 'React'.padStart(10, 'Hello')      //'HelloReact'

  3. 'JavaScript'.padStart(10, 'Hi')    //'JavaScript'

  4. 'JavaScript'.padStart(8, 'Hi')     //'JavaScript'

從上面結果來看,填充函數只有在字符長度小於目標長度時纔有效,若字符長度已經等於或小於目標長度時,填充字符不會起做用,並且目標長度若是小於字符串自己長度時,字符串也不會作截斷處理,只會原樣輸出。

padEnd函數做用同 padStart,只不過它是從字符串尾部作填充。來看個小例子:

  1. 'Vue'.padEnd(10, '_*')           //'Vue_*_*_*_'

  2. 'React'.padEnd(10, 'Hello')      //'ReactHello'

  3. 'JavaScript'.padEnd(10, 'Hi')    //'JavaScript'

  4. 'JavaScript'.padEnd(8, 'Hi')     //'JavaScript'

Object.getOwnPropertyDescriptors()

顧名思義,該方法會返回目標對象中全部屬性的屬性描述符,該屬性必須是對象本身定義的,不能是從原型鏈繼承來的。先來看個它的基本用法:

  1. let obj = {

  2.  id: 1,

  3.  name: 'test',

  4.  get gender() {

  5.    console.log('gender')

  6.  },

  7.  set grade(g) {

  8.    console.log(g)

  9.  }

  10. }

  11. Object.getOwnPropertyDescriptors(obj)

  12.  

  13. //輸出結果爲:

  14. {

  15.  gender: {

  16.    configurable: true,

  17.    enumerable: true,

  18.    get: f gender(),

  19.    set: undefined

  20.  },

  21.  grade: {

  22.    configurable: true,

  23.    enumerable: true,

  24.    get: undefined,

  25.    set: f grade(g)

  26.  },

  27.  id: {

  28.    configurable: true,

  29.    enumerable: true,

  30.    value: 1,

  31.    writable: true

  32.  },

  33.  name: {

  34.    configurable: true,

  35.    enumerable: true,

  36.    value: 'test',

  37.    writable: true

  38.  }

  39. }

方法還提供了第二個參數,用來獲取指定屬性的屬性描述符。

  1. let obj = {

  2.  id: 1,

  3.  name: 'test',

  4.  get gender() {

  5.    console.log('gender')

  6.  },

  7.  set grade(g) {

  8.    console.log(g)

  9.  }

  10. }

  11. Object.getOwnPropertyDescriptors(obj, 'id')

  12.  

  13. //輸出結果爲:

  14. {

  15.  id: {

  16.    configurable: true,

  17.    enumerable: true,

  18.    value: 1,

  19.    writable: true

  20.  }

  21. }

由上述例子可知,該方法返回的描述符,會有兩種類型:數據描述符、存取器描述符。返回結果中包含的鍵可能的值有:configurable、enumerable、value、writable、get、set。

使用過 Object.assign()的同窗都知道,assign方法只能拷貝一個屬性的值,而不會拷貝它背後的複製方法和取值方法。 Object.getOwnPropertyDescriptors()主要是爲了解決 Object.assign()沒法正確拷貝 get屬性和 set屬性的問題。

  1. let obj = {

  2.  id: 1,

  3.  name: 'test',

  4.  get gender() {

  5.    console.log('gender')

  6.  }

  7. }

  8. Object.assign(obj)

  9.  

  10. //輸出結果爲:

  11. {

  12.  gender: undefined

  13.  id: 1,

  14.  name: 'test'

  15. }

此時, Object.getOwnPropertyDescriptors方法配合 Object.defineProperties方法,就能夠實現正確拷貝。

  1. let obj = {

  2.  id: 1,

  3.  name: 'test',

  4.  get gender() {

  5.    console.log('gender')

  6.  }

  7. }

  8. let obj1 = {}

  9. Object.defineProperties(obj1, Object.getOwnPropertyDescriptors(obj))

  10. Object.getOwnPropertyDescriptors(obj1)

  11.  

  12. //輸出結果爲:

  13. {

  14.  gender: {

  15.    configurable: true,

  16.    enumerable: true,

  17.    get: f gender(),

  18.    set: undefined

  19.  },

  20.  id: {

  21.    configurable: true,

  22.    enumerable: true,

  23.    value: 1,

  24.    writable: true

  25.  },

  26.  name: {

  27.    configurable: true,

  28.    enumerable: true,

  29.    value: 'test',

  30.    writable: true

  31.  }

  32. }

上述代碼演示了,咱們如何來拷貝一個屬性值爲賦值方法或者取值方法的對象。更多 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

函數參數列表與調用中的尾部逗號

該特性容許咱們在定義或者調用函數時添加尾部逗號而不報錯。

  1. let foo = function (

  2.  a,

  3.  b,

  4.  c,

  5. ) {

  6.  console.log('a:', a)

  7.  console.log('b:', b)

  8.  console.log('c:', c)

  9. }

  10. foo(1, 3, 4, )

  11.  

  12. //輸出結果爲:

  13. a: 1

  14. b: 3

  15. c: 4

上面這種方式調用是沒有問題的。函數的這種尾逗號也是向數組和字面量對象中尾逗號看齊,它適用於那種多行參數而且參數名很長的狀況,開發過程當中,若是忘記刪除尾部逗號也不要緊,ES8已經支持這種寫法。

這麼用有什麼好處呢?

首先,當咱們調整結構時,不會由於最後一行代碼的位置變更,而去添加或者刪除逗號。

其次,在版本管理上,不會出現由於一個逗號,而使原本只有一行的修改,變成兩行。例以下面:

  1. (

  2.  'abc'

  3. )

  1. (

  2.  'abc',

  3.  'def'

  4. )

在咱們版本管理系統裏,它會監測到你有兩處更改,可是若是咱們沒必要去關心逗號的存在,每一行都有逗號時,新加一行,也只會監測到一行的修改。

建議的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

相關文章
相關標籤/搜索