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

sunshine楊小咩發表於2019-02-28

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

某些JavaScript(ECMAScript)功能比其他功能更容易理解。Generators看起來很奇怪-- 就像C/C ++中的指標一樣。Symbols看起來像原始值和物件。

這些功能都是相互關聯的,並且相互構建。所以如果不理解一件事你就無法理解另一件事

所以在本文中,將介紹symbols,global-symbols,iterators,iterables,generators ,async/await 和async iterators。將首先解釋“ 為什麼 ”,並通過一些有用的例子展示他們是如何工作的。

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

好的,讓我們開始吧

Symbols

在ES2015中,建立了一個新的(第6個)資料型別symbol

為什麼?

三個主要原因是:

原因1 - 新增具有向後相容性的新核心功能

JavaScript開發人員和ECMAScript委員會(TC39)需要一種方法來新增新的物件屬性,而不會破壞現有方法像for...in迴圈或JavaScript方法像Object.keys

例如,如果一個物件,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.然後自定義函式可能會因名稱衝突而中斷。

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

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

原因3 - 通過“眾所周知的”符號(“Well-known” Symbols)允許鉤子呼叫核心方法

假設需要一些核心方法,比如String.prototype.search呼叫自定義函式。也就是說,‘somestring’.search(myObject);應該呼叫myObject的搜尋函式並將 ‘somestring’作為引數傳遞,我們該怎麼做?

這就是ES2015提出了一系列稱為“眾所周知”的Symbols的全域性Symbols。只要你的物件將其中一個Symbols作為屬性,就可以重新定位核心函式來呼叫你的函式。

我們現在先不談論這個問題,將在本文後面詳細介紹所有細節。但首先,讓我們瞭解Symbols實際是如何工作的。

建立Symbols

可以通過呼叫Symbol全域性函式/物件來建立符號Symbol 。該函式返回資料型別的值symbol

[譯]JavaScript Symbols, Iterators, Generators, Async/Await, and Async Iterators
注意: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也不是唯一的。

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

Symbols可以是一個物件屬性鍵

這對於Symbols來說是一個非常獨特的東西———— 也是最令人困惑的。雖然它們看起來像一個物件,但它們是原始的。我們可以將Symbol作為屬性鍵新增到物件,就像String一樣。

實際上,這是使用Symbols的主要方式之一 ,作為物件屬性。

[譯]JavaScript Symbols, Iterators, Generators, Async/Await, and Async Iterators
注意:使用Symbols的物件屬性是“鍵屬性”。

[]操作符與.操作符

不能使用.操作符,因為.操作符僅適用於字串屬性,因此應使用[]操作符。

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

使用Symbol的3個主要原因 - review

讓我們回顧一下的三個主要原因來了解Symbol是如何工作的。

原因1 - Symbols對於迴圈和其他方法是不可見的

下面示例中使用for-in迴圈遍歷一個物件obj,但它不知道(或忽略)prop3,prop4因為它們是symbols。

[譯]JavaScript Symbols, Iterators, Generators, Async/Await, and Async Iterators
下面是另一個示例,其中Object.keysObject.getOwnPropertyNames忽略了Symbol的屬性名稱。

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

原因2 - Symbols是唯一的

假設想要在全域性Array物件上呼叫Array.prototype.includes的功能。它將與JavaScript(ES2018)預設方法includes衝突。如何在不衝突的情況下新增它?

首先,建立一個具有合適名稱的變數includes併為其指定一個symbol。然後將此變數(現在是symbol)新增到全域性Array使用括號表示法。分配想要的任何功能。

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

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

原因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或字串,並返回索引(如果找到)。

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

在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到我們的程式碼。

下面的虛擬碼片段顯示了程式碼內部的工作方式:

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

不是一定需要通過RegExp。可以傳遞任何實現Symbol.search並返回任何所需內容的自定義物件。

自定義String.search方法來呼叫自定義函式

下面的例子展示了我們如何使String.prototype.search呼叫自定義Product類的搜尋功能 - 多虧了Symbol.search全域性Symbol

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

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方法。

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

但是,能夠在我們自己的物件中使用這些現有方法不是很好嗎?為了實現這一點,我們需要制定所有開發人員可以遵循的規則,並使其物件與現有方法一起使用。

如果他們遵循這些規則從物件中提取資料,那麼這些物件稱為“迭代”。

規則是:

  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物件和迭代:

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

重要說明:如果我們傳遞一個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方法。

[譯]JavaScript Symbols, Iterators, Generators, Async/Await, and Async Iterators
在類中使用generator

例2:

可以進一步簡化它。使函式成為generator(帶*語法),並使用一次yield返回一個值,如下所示。

[譯]JavaScript Symbols, Iterators, Generators, Async/Await, and Async Iterators
直接使用Generators作為函式
重要說明:雖然在上面的例子中,使用“iterator”這個詞來表示allUsers ,但它確實是一個generator物件。

generator物件具有方法throw和方法return之外的next方法,但是出於實際目的,我們可以將返回的物件用作“迭代器”。

原因2 - 提供更好和更新的控制流程

幫助提供新的控制流程,幫助我們以新的方式編寫程式並解決諸如“回撥地獄”之類的問題。

請注意,與普通函式不同,generator函式可以yield(儲存函式statereturn值)並準備好在其產生的點處獲取其他輸入值。

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

[譯]JavaScript Symbols, Iterators, Generators, Async/Await, and Async Iterators
正常函式與generator函式

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

[譯]JavaScript Symbols, Iterators, Generators, Async/Await, and Async Iterators
generator控制流程

generator語法和用法

generator功能可以通過以下方式使用:

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

我們可以在“yield”之後獲得更多程式碼(與“return”語句不同)

就像return關鍵字一樣,yield關鍵字也會返回值 - 但它允許我們在yielding之後擁有程式碼

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

可以有多個yield

[譯]JavaScript Symbols, Iterators, Generators, Async/Await, and Async Iterators
可以有多個yield語句

通過next方法向generators來回傳送值

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

事實上,這個功能使generator能夠消除“回撥地獄”。稍後將瞭解更多相關資訊。

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

在下面的示例中,我們使用空next()呼叫來呼叫迭代器。然後,當我們第二次呼叫時傳遞23作為引數next(23)

[譯]JavaScript Symbols, Iterators, Generators, Async/Await, and Async Iterators
通過next從外部將值傳回generator

generator幫助消除“回撥地獄”

如果有多個非同步呼叫,會進入回撥地獄。

下面的示例顯示了諸如“co”之類的庫如何使用generator功能,該功能允許我們通過該next方法傳遞值以幫助我們同步編寫非同步程式碼。

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

[譯]JavaScript Symbols, Iterators, Generators, Async/Await, and Async Iterators
制流程像“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更優雅和簡單。

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

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工作原理以及如何使用它。
    [譯]JavaScript Symbols, Iterators, Generators, Async/Await, and Async Iterators
    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中的“類”是新的“壞”部分嗎?

相關文章