[譯]JavaScript Symbols, Iterators, Generators, Async/Await, and Async Iterators 

原文地址:簡單解釋JavaScript Symbols, Iterators, Generators, Async/Await, and Async Iteratorsjavascript

某些JavaScript(ECMAScript)功能比其餘功能更容易理解。Generators看起來很奇怪-- 就像C/C ++中的指針同樣。Symbols看起來像原始值和對象。java

這些功能都是相互關聯的,而且相互構建。因此若是不理解一件事你就沒法理解另外一件事es6

因此在本文中,將介紹symbols,global-symbols,iterators,iterables,generators ,async/await 和async iterators。將首先解釋「 爲何 」,並經過一些有用的例子展現他們是如何工做的。redux

這是一個相對高階的主題,但它並不複雜。本文應該讓你很好地掌握全部這些概念。數組

好的,讓咱們開始吧promise

Symbols

在ES2015中,建立了一個新的(第6個)數據類型symbol瀏覽器

爲何?

三個主要緣由是:bash

緣由1 - 添加具備向後兼容性的新核心功能

JavaScript開發人員和ECMAScript委員會(TC39)須要一種方法來添加新的對象屬性,而不會破壞現有方法像for...in循環或JavaScript方法像Object.keysecmascript

例如,若是一個對象,var myObject = {firstName:'raja', lastName:'rao'} 運行Object.keys(myObject)它將返回[firstName, lastName]異步

如今,若是咱們添加另外一個屬性,爲myObject設置newProperty ,若是運行Object.keys(myObject)它應該仍然返回舊值(即,以某種方式使之忽略新加入的newproperty),而且只顯示[firstName, lastName]而不是[firstName, lastName, newProperty] 。這該如何作?

咱們以前沒法真正作到這一點,所以建立了一個名爲Symbols的新數據類型。

若是做爲symbol添加newProperty,那麼Object.keys(myObject)會忽略它(由於它不識別它),仍然返回[firstName, lastName]

緣由2 - 避免名稱衝突

他們還但願保持這些屬性的獨特性。經過這種方式,能夠繼續向全局添加新屬性(而且能夠添加對象屬性),而無需擔憂名稱衝突。

例如,假設有一個自定義的對象,將在對象中將自定義toUpperCase函數添加到全局Array.prototype

如今,假設加載了另外一個庫(或着ES2019發佈的庫)而且它有與自定義函數不一樣的Array.prototype.toUpperCase.而後自定義函數可能會因名稱衝突而中斷。

那怎麼解決這個可能不知道的名稱衝突?這就是Symbols用武之地。它們在內部建立了獨特的值,容許建立添加屬性而沒必要擔憂名稱衝突。

緣由3 - 經過「衆所周知的」符號(「Well-known」 Symbols)容許鉤子調用核心方法

假設須要一些核心方法,好比String.prototype.search調用自定義函數。也就是說,‘somestring’.search(myObject);應該調用myObject的搜索函數並將 ‘somestring’做爲參數傳遞,咱們該怎麼作?

這就是ES2015提出了一系列稱爲「衆所周知」的Symbols的全局Symbols。只要你的對象將其中一個Symbols做爲屬性,就能夠從新定位核心函數來調用你的函數。

咱們如今先不談論這個問題,將在本文後面詳細介紹全部細節。但首先,讓咱們瞭解Symbols實際是如何工做的。

建立Symbols

能夠經過調用Symbol全局函數/對象來建立符號Symbol 。該函數返回數據類型的值symbol

注意:Symbols可能看起來像對象,由於它們有方法,但它們不是 - 它們是原始的。能夠將它們視爲與常規對象具備某些類似性的「特殊」對象,但它們的行爲與常規對象不一樣。

例如:Symbols具備與對象相似的方法,但與對象不一樣,它們是不可變的且惟一的。

「new」關鍵字沒法建立Symbols

由於Symbols不是對象而new關鍵字應該返回Object,因此咱們不能經過new來返回symbols 數據類型。

var mySymbol = new Symbol(); //拋出錯誤

Symbols有「描述」

Symbols能夠有描述 - 它只是用於記錄目的。

// mySymbol變量如今包含一個「Symbols」惟一值
//它的描述是「some text」 
const mySymbol = Symbol('some text');
複製代碼

Symbols是惟一的

const mySymbol1 =Symbols('some text'); 
const mySymbol2 =Symbols('some text'); 
mySymbol1 == mySymbol2 // false
複製代碼

若是咱們使用「Symbol.for」方法,Symbols表現的就像一個單例

若是不經過Symbol()建立Symbol,能夠經過Symbol.for(<key>)建立symbol, 。這須要一個「key」(字符串)來建立一個Symbol。若是一個key對應的Symbol已經存在,它只返回舊Symbol。所以,若是咱們使用該Symbol.for方法,它就像一個單例。

var mySymbol1 = Symbol .for('some key'); //建立一個新symbol
var mySymbol2 = Symbol .for('some key'); // ** 返回相同的symbol
 mySymbol1 == mySymbol2 // true
複製代碼

使用 .for真正緣由是在一個地方建立一個Symbols,並從其餘地方訪問相同的Symbols。

注意Symbol.for若是鍵是相同的,將覆蓋以前的值,這將使Symbol非惟一,因此儘量避免這種狀況。

Symbols的key與描述

若只是爲了讓事情更清楚,若是不使用Symbol.for ,那麼Symbol是惟一的。可是,若是使用Symbol.for,並且key 不是惟一的,則返回的Symbol也不是惟一的。

Symbols能夠是一個對象屬性鍵

這對於Symbols來講是一個很是獨特的東西———— 也是最使人困惑的。雖然它們看起來像一個對象,但它們是原始的。咱們能夠將Symbol做爲屬性鍵添加到對象,就像String同樣。

實際上,這是使用Symbols的主要方式之一 ,做爲對象屬性。

注意:使用Symbols的對象屬性是「鍵屬性」。

[]操做符與.操做符

不能使用.操做符,由於.操做符僅適用於字符串屬性,所以應使用[]操做符。

使用Symbol的3個主要緣由 - review

讓咱們回顧一下的三個主要緣由來了解Symbol是如何工做的。

緣由1 - Symbols對於循環和其餘方法是不可見的

下面示例中使用for-in循環遍歷一個對象obj,但它不知道(或忽略)prop3,prop4由於它們是symbols。

下面是另外一個示例,其中 Object.keysObject.getOwnPropertyNames忽略了Symbol的屬性名稱。

緣由2 - Symbols是惟一的

假設想要在全局Array對象上調用Array.prototype.includes的功能。它將與JavaScript(ES2018)默認方法includes衝突。如何在不衝突的狀況下添加它?

首先,建立一個具備合適名稱的變量includes併爲其指定一個symbol。而後將此變量(如今是symbol)添加到全局Array使用括號表示法。分配想要的任何功能。

最後使用括號表示法調用該函數。但請注意,必須在括號內傳遞實際symbol,如:arr[includes]()而不是字符串。

緣由3-衆所周知的Symbols(即「全局」symbols)

默認狀況下,JavaScript會自動建立一堆Symbols變量並將它們分配給全局Symbol對象(使用相同的Symbol()去建立Symbols)。

ECMAScript 2015,這些Symbols被加入到核心對象如數組和字符串的核心方法如String.prototype.searchString.prototype.replace

這些symbols的一些例子是:Symbol.match,Symbol.replace,Symbol.search,Symbol.iterator和Symbol.split

因爲這些全局Symbols是全局的而且是公開的,咱們可使用核心方法調用自定義函數而不是內部函數。

一個例子: Symbol.search

例如,String對象的String.prototype.search公共方法搜索regExp或字符串,並返回索引(若是找到)。

在ES2015中,它首先檢查是否在查詢regExp(RegExp對象)中實現了Symbol.search方法。若是實現了,那麼它調用該函數並將工做委託給它。像RegExp這樣的核心對象實現了實際完成工做的SymbolSymbol.search

Symbol.search的內部工做原理

  1. 解析 ‘rajarao’.search(‘rao’);
  2. 「rajarao」轉換爲String對象 new String(「rajarao」)
  3. 「rao」轉換爲RegExp對象 new Regexp(「rao」)
  4. 調用字符串對象「rajarao」的方法search,傳遞'rao'對象爲參數。
  5. search方法調用「rao」對象內部方法Symbol.search(將搜索委託返回「rao」對象)並傳遞「rajarao」。像這樣:"rao"[Symbol.search]("rajarao")
  6. "rao"[Symbol.search]("rajarao")返回索引結果4傳遞給search函數,最後,search返回4到咱們的代碼。

下面的僞代碼片斷顯示了代碼內部的工做方式:

不是必定須要經過RegExp。能夠傳遞任何實現Symbol.search並返回任何所需內容的自定義對象。

自定義String.search方法來調用自定義函數

下面的例子展現了咱們如何使String.prototype.search調用自定義Product類的搜索功能 - 多虧了Symbol.search全局Symbol

Symbol.search(CUSTOM BEHAVIOR)的內部工做原理

  1. 解析 ‘barsoap’.search(soapObj);
  2. 「barsoap」轉換爲String對象new String(「barsoap」)
  3. 因爲soapObj已是對象,不要進行任何轉換
  4. 調用「barsoap」字符串對象的search方法。
  5. search方法調用「soapObj」對象內部方法Symbol.search(它將搜索委託回「soapObj」對象)並傳遞「barsoap」做爲參數。像這樣:soapObj[Symbol.search]("barsoap")
  6. soapObj[Symbol.search]("barsoap")返回索引結果FOUNDsearch函數,最後,search返回FOUND到咱們的代碼。

好的,讓咱們轉到Iterators。

迭代器和Iterables

爲何?

在幾乎全部的應用程序中,咱們都在不斷處理數據列表,咱們須要在瀏覽器或移動應用程序中顯示這些數據。一般咱們編寫本身的方法來存儲和提取數據。

但問題是,咱們已經有了for-of循環和擴展運算符(…)等標準方法來從標準對象(如數組,字符串和映射)中提取數據集合。爲何咱們不能將這些標準方法用於咱們的Object?

在下面的示例中,咱們不能使用for-of循環或(…)運算符來從Users類中提取數據。咱們必須使用自定義get方法。

可是,可以在咱們本身的對象中使用這些現有方法不是很好嗎?爲了實現這一點,咱們須要制定全部開發人員能夠遵循的規則,並使其對象與現有方法一塊兒使用。

若是他們遵循這些規則從對象中提取數據,那麼這些對象稱爲「迭代」。

規則是:

  1. 主對象/類應該存儲一些數據。
  2. 主對象/類必須具備全局「衆所周知的」Symbolssymbol.iterator做爲其屬性,Symbols根據規則#3至#6實現特定方法。
  3. symbol.iterator方法必須返回另外一個對象 - 「迭代器」對象。
  4. 這個「迭代器」對象必須有一個稱爲next的方法。
  5. next方法應該能夠訪問存儲在規則1中的數據。
  6. 若是咱們調用iteratorObj.next(),它應該返回規則#1中的一些存儲數據不管是想要返回更多值{value:<stored data>, done: false},仍是不想返回任何數據{done: true}

若是遵循全部這6個規則,則來自規則#1的主要對象被稱爲 可迭代。 它返回的對象稱爲迭代器

咱們來看看如何建立Users對象和迭代:

重要說明:若是咱們傳遞一個iterable(allUsers)for-of 循環或擴展運算符,將會在內部調用<iterable>[Symbol.iterator]()獲取迭代器(如allUsersIterator),而後使用迭代器提取數據。

因此在某種程度上,全部這些規則都有一個返回iterator對象的標準方法。

Generator 函數

爲何?

主要有兩個緣由:

  1. 爲迭代提供更高級別的抽象
  2. 提供更新的控制流來幫助解決諸如「回調地獄」之類的問題。

咱們來看看它的詳細內容。

緣由1 - 迭代的包裝器

不是經過遵循全部這些規則來使咱們的類/對象成爲一個iterable,咱們能夠簡單地建立一個「Generator」方法來簡化這件事情。

如下是關於Generator的一些要點:

  1. Generator方法在內部有一個*<myGenerator>新語法,Generator函數有語法function * myGenerator(){}
  2. 調用generatormyGenerator()返回一個實現iterator協議(規則)的generator對象,所以咱們能夠將其用做iterator開箱即用的返回值。
  3. generator使用特殊yield語句來返回數據。
  4. yield 語句保持之前的調用狀態,並從它中止的地方繼續。
  5. 若是yield在循環中使用它,它只會在每次咱們在調迭代器上調用next()方法時執行一次。

例1:

下面的代碼展現瞭如何使用generator方法(*getIterator())實現遵循全部規則的next的方法,而不是使用Symbol.iterator方法。

在類中使用generator

例2:

能夠進一步簡化它。使函數成爲generator(帶*語法),並使用一次yield返回一個值,以下所示。

直接使用Generators做爲函數
重要說明:雖然在上面的例子中,使用 「iterator」這個詞來表示 allUsers ,但它確實是一個 generator對象。

generator對象具備方法throw和方法return以外的next方法,可是出於實際目的,咱們能夠將返回的對象用做「迭代器」。

緣由2 - 提供更好和更新的控制流程

幫助提供新的控制流程,幫助咱們以新的方式編寫程序並解決諸如「回調地獄」之類的問題。

請注意,與普通函數不一樣,generator函數能夠yield(存儲函數statereturn值)並準備好在其產生的點處獲取其餘輸入值。

在下面的圖片中,每次看到yield它均可以返回值。可使用generator.next(「some new value」)在它產生的位置使用並傳遞新值。

正常函數與generator函數

如下示例更具體地說明了控制流如何工做:

generator控制流程

generator語法和用法

generator功能能夠經過如下方式使用:

咱們能夠在「yield」以後得到更多代碼(與「return」語句不一樣)

就像return關鍵字同樣,yield關鍵字也會返回值 - 但它容許咱們在yielding以後擁有代碼

能夠有多個yield

能夠有多個yield語句

經過next方法向generators來回發送值

迭代器next方法還能夠將值傳遞迴generator,以下所示。

事實上,這個功能使generator可以消除「回調地獄」。稍後將瞭解更多相關信息。

此功能也在redux-saga等庫中大量使用。

在下面的示例中,咱們使用空next()調用來調用迭代器。而後,當咱們第二次調用時傳遞23做爲參數next(23)

經過 next從外部將值傳回generator

generator幫助消除「回調地獄」

若是有多個異步調用,會進入回調地獄。

下面的示例顯示了諸如「co」之類的庫如何使用generator功能,該功能容許咱們經過該next方法傳遞值以幫助咱們同步編寫異步代碼。

注意co函數如何經過next(result)步驟5和步驟10 將結果從promise發送回generator。

制流程像 「co」這樣使用 「next(<someval>)」lib的逐步解釋

好的,讓咱們繼續async / await

異步/ AWAIT

爲何?

正如以前看到的,Generators能夠幫助消除「回調地獄」,但須要一些第三方庫co來實現這一點。可是「回調地獄」是一個很大的問題,ECMAScript委員會決定爲Generator建立一個包裝器並推出新的關鍵字async/await

GeneratorsAsync / Await之間的區別是:

  1. async / await使用await而不是yield
  2. await僅適用於Promises
  3. Async / Await使用async function關鍵字,而不是function*

因此async/await基本上是Generators的一個子集,而且有一個新的語法糖。

async關鍵字告訴JavaScript編譯器以不一樣方式處理該函數。只要到達await函數中的關鍵字,編譯器就會暫停。它假定表達式await返回一個promise並等待,直到promise被解決或拒絕,而後才進一步移動。

在下面的示例中,getAmount函數正在調用兩個異步函數getUsergetBankBalance 。咱們能夠在promise中作到這一點,但使用async await更優雅和簡單。

ASYNC ITERATORS

爲何?

這是一個很是常見的場景,咱們須要在循環中調用異步函數。所以,在ES2018(已完成的提案)中,TC39委員會提出了一個新的Symbol Symbol.asyncIterator和一個新的構造,for-await-of以幫助咱們輕鬆地循環異步函數。

常規Iterator對象和異步迭代器之間的主要區別以下:

Iterator對象

  1. Iterator對象的next()方法返回值如{value: ‘some val’, done: false}
  2. 用法: iterator.next() //{value: ‘some val’, done: false}

Async Iterator對象

  1. Async Iterator對象的next()方法返回一個Promise,後來解析成相似的{value: ‘some val’, done: false}
  2. 用法: iterator.next().then(({ value, done })=> {//{value: ‘some val’, done: false}} 如下示例顯示了for-await-of工做原理以及如何使用它。
    for-await-of(ES2018)

總結

Symbol - 提供全局惟一的數據類型。主要使用它們做爲對象屬性來添加新行爲,所以不會破壞像Object.keys和for-in循環這樣的標準方法。

衆所周知的Symbols- 由JavaScript自動生成的Symbols,可用於在咱們的自定義對象中實現核心方法

Iterables- 是存儲數據集合並遵循特定規則的任何對象,以便咱們可使用標準for-of循環和...擴展運算符從中提取數據。

Iterators- 由Iterables返回並具備next方法它其實是從Iterables中提取數據。

Generator -爲Iterables提供更高級別的抽象。它們還提供了新的控制流,能夠解決諸如回調地獄之類的問題,併爲諸如此類的事物提供構建塊Async/Await。

Async/Await- 爲generator提供更高級別的抽象,以便專門解決回調地獄問題。

Async迭代器- 一種全新的2018功能,可幫助循環異步函數數組,以得到每一個異步函數的結果,就像在普通循環中同樣。

進一步閱讀

ECMAScript 2015+

  1. 如下是ECMAScript 2016,2017和2018中全部新功能的示例
  2. 查看這些有用的ECMAScript 2015(ES6)提示和技巧
  3. 5個在ES6中修復的JavaScript「壞」部分
  4. ES6中的「類」是新的「壞」部分嗎?
相關文章
相關標籤/搜索