原文地址:簡單解釋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
在ES2015中,建立了一個新的(第6個)數據類型symbol
。瀏覽器
三個主要緣由是:bash
JavaScript開發人員和ECMAScript委員會(TC39)須要一種方法來添加新的對象屬性,而不會破壞現有方法像for...in
循環或JavaScript方法像Object.keys
。ecmascript
例如,若是一個對象,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]
。
他們還但願保持這些屬性的獨特性。經過這種方式,能夠繼續向全局添加新屬性(而且能夠添加對象屬性),而無需擔憂名稱衝突。
例如,假設有一個自定義的對象,將在對象中將自定義toUpperCase
函數添加到全局Array.prototype
。
如今,假設加載了另外一個庫(或着ES2019發佈的庫)而且它有與自定義函數不一樣的Array.prototype.toUpperCase
.而後自定義函數可能會因名稱衝突而中斷。
那怎麼解決這個可能不知道的名稱衝突?這就是Symbols
用武之地。它們在內部建立了獨特的值,容許建立添加屬性而沒必要擔憂名稱衝突。
假設須要一些核心方法,好比String.prototype.search
調用自定義函數。也就是說,‘somestring’.search(myObject)
;應該調用myObject
的搜索函數並將 ‘somestring’
做爲參數傳遞,咱們該怎麼作?
這就是ES2015提出了一系列稱爲「衆所周知」的Symbols的全局Symbols。只要你的對象將其中一個Symbols做爲屬性,就能夠從新定位核心函數來調用你的函數。
咱們如今先不談論這個問題,將在本文後面詳細介紹全部細節。但首先,讓咱們瞭解Symbols實際是如何工做的。
能夠經過調用Symbol
全局函數/對象來建立符號Symbol
。該函數返回數據類型的值symbol
。
例如:Symbols具備與對象相似的方法,但與對象不一樣,它們是不可變的且惟一的。
由於Symbols不是對象而new關鍵字應該返回Object,因此咱們不能經過new來返回symbols 數據類型。
var mySymbol = new Symbol(); //拋出錯誤
Symbols能夠有描述 - 它只是用於記錄目的。
// mySymbol變量如今包含一個「Symbols」惟一值
//它的描述是「some text」
const mySymbol = Symbol('some text');
複製代碼
const mySymbol1 =Symbols('some text');
const mySymbol2 =Symbols('some text');
mySymbol1 == mySymbol2 // false
複製代碼
若是不經過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非惟一,因此儘量避免這種狀況。
若只是爲了讓事情更清楚,若是不使用Symbol.for
,那麼Symbol是惟一的。可是,若是使用Symbol.for
,並且key
不是惟一的,則返回的Symbol也不是惟一的。
這對於Symbols來講是一個很是獨特的東西———— 也是最使人困惑的。雖然它們看起來像一個對象,但它們是原始的。咱們能夠將Symbol做爲屬性鍵添加到對象,就像String
同樣。
實際上,這是使用Symbols
的主要方式之一 ,做爲對象屬性。
不能使用.操做符,由於.操做符僅適用於字符串屬性,所以應使用[]操做符。
讓咱們回顧一下的三個主要緣由來了解Symbol是如何工做的。
下面示例中使用for-in
循環遍歷一個對象obj
,但它不知道(或忽略)prop3,prop4
由於它們是symbols。
Object.keys
和
Object.getOwnPropertyNames
忽略了Symbol的屬性名稱。
假設想要在全局Array
對象上調用Array.prototype.includes
的功能。它將與JavaScript(ES2018)默認方法includes
衝突。如何在不衝突的狀況下添加它?
首先,建立一個具備合適名稱的變量includes
併爲其指定一個symbol。而後將此變量(如今是symbol)添加到全局Array
使用括號表示法。分配想要的任何功能。
最後使用括號表示法調用該函數。但請注意,必須在括號內傳遞實際symbol,如:arr[includes]()
而不是字符串。
默認狀況下,JavaScript會自動建立一堆Symbols變量並將它們分配給全局Symbol
對象(使用相同的Symbol()去建立Symbols)。
ECMAScript 2015,這些Symbols被加入到核心對象如數組和字符串的核心方法如String.prototype.search
與String.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
。
‘rajarao’.search(‘rao’);
「rajarao」
轉換爲String對象 new String(「rajarao」)
「rao」
轉換爲RegExp對象 new Regexp(「rao」)
「rajarao」
的方法search
,傳遞'rao'
對象爲參數。search
方法調用「rao」對象內部方法Symbol.search
(將搜索委託返回「rao」對象)並傳遞「rajarao」
。像這樣:"rao"[Symbol.search]("rajarao")
"rao"[Symbol.search]("rajarao")
返回索引結果4傳遞給search
函數,最後,search
返回4到咱們的代碼。下面的僞代碼片斷顯示了代碼內部的工做方式:
不是必定須要經過RegExp。能夠傳遞任何實現Symbol.search並返回任何所需內容的自定義對象。
下面的例子展現了咱們如何使String.prototype.search
調用自定義Product類的搜索功能 - 多虧了Symbol.search
全局Symbol
。
‘barsoap’.search(soapObj)
;「barsoap」
轉換爲String對象new String(「barsoap」)
soapObj
已是對象,不要進行任何轉換search
方法。search
方法調用「soapObj」對象內部方法Symbol.search
(它將搜索委託回「soapObj」對象)並傳遞「barsoap」
做爲參數。像這樣:soapObj[Symbol.search]("barsoap")
soapObj[Symbol.search]("barsoap")
返回索引結果FOUND
給search
函數,最後,search
返回FOUND
到咱們的代碼。好的,讓咱們轉到Iterators。
在幾乎全部的應用程序中,咱們都在不斷處理數據列表,咱們須要在瀏覽器或移動應用程序中顯示這些數據。一般咱們編寫本身的方法來存儲和提取數據。
但問題是,咱們已經有了for-of
循環和擴展運算符(…)
等標準方法來從標準對象(如數組,字符串和映射)中提取數據集合。爲何咱們不能將這些標準方法用於咱們的Object?
在下面的示例中,咱們不能使用for-of
循環或(…)
運算符來從Users
類中提取數據。咱們必須使用自定義get
方法。
可是,可以在咱們本身的對象中使用這些現有方法不是很好嗎?爲了實現這一點,咱們須要制定全部開發人員能夠遵循的規則,並使其對象與現有方法一塊兒使用。
若是他們遵循這些規則從對象中提取數據,那麼這些對象稱爲「迭代」。
規則是:
symbol.iterator
做爲其屬性,Symbols根據規則#3至#6實現特定方法。symbol.iterator
方法必須返回另外一個對象 - 「迭代器」對象。next
的方法。next
方法應該能夠訪問存儲在規則1中的數據。iteratorObj.next()
,它應該返回規則#1中的一些存儲數據不管是想要返回更多值{value:<stored data>, done: false}
,仍是不想返回任何數據{done: true}
。若是遵循全部這6個規則,則來自規則#1的主要對象被稱爲 可迭代。 它返回的對象稱爲迭代器。
咱們來看看如何建立Users
對象和迭代:
重要說明:若是咱們傳遞一個iterable(allUsers)for-of
循環或擴展運算符,將會在內部調用<iterable>[Symbol.iterator]()
獲取迭代器(如allUsersIterator
),而後使用迭代器提取數據。
因此在某種程度上,全部這些規則都有一個返回iterator
對象的標準方法。
主要有兩個緣由:
咱們來看看它的詳細內容。
不是經過遵循全部這些規則來使咱們的類/對象成爲一個iterable
,咱們能夠簡單地建立一個「Generator」方法來簡化這件事情。
如下是關於Generator的一些要點:
Generator
方法在內部有一個*<myGenerator>
新語法,Generator
函數有語法function * myGenerator(){}
。myGenerator()
返回一個實現iterator協議(規則)的generator
對象,所以咱們能夠將其用做iterator
開箱即用的返回值。下面的代碼展現瞭如何使用generator方法(*getIterator())
實現遵循全部規則的next
的方法,而不是使用Symbol.iterator
方法。
能夠進一步簡化它。使函數成爲generator(帶*語法),並使用一次yield
返回一個值,以下所示。
「iterator」
這個詞來表示
allUsers
,但它確實是一個
generator
對象。
generator對象具備方法throw
和方法return
以外的next
方法,可是出於實際目的,咱們能夠將返回的對象用做「迭代器」。
幫助提供新的控制流程,幫助咱們以新的方式編寫程序並解決諸如「回調地獄」之類的問題。
請注意,與普通函數不一樣,generator函數能夠yield
(存儲函數state
和return
值)並準備好在其產生的點處獲取其餘輸入值。
在下面的圖片中,每次看到yield它均可以返回值。可使用generator.next(「some new value」)
在它產生的位置使用並傳遞新值。
如下示例更具體地說明了控制流如何工做:
generator功能能夠經過如下方式使用:
就像return
關鍵字同樣,yield
關鍵字也會返回值 - 但它容許咱們在yielding以後擁有代碼
yield
next
方法向generators來回發送值迭代器next
方法還能夠將值傳遞迴generator,以下所示。
事實上,這個功能使generator可以消除「回調地獄」。稍後將瞭解更多相關信息。
此功能也在redux-saga等庫中大量使用。
在下面的示例中,咱們使用空next()
調用來調用迭代器。而後,當咱們第二次調用時傳遞23
做爲參數next(23)
。
next
從外部將值傳回generator
若是有多個異步調用,會進入回調地獄。
下面的示例顯示了諸如「co」
之類的庫如何使用generator功能,該功能容許咱們經過該next
方法傳遞值以幫助咱們同步編寫異步代碼。
注意co
函數如何經過next(result)
步驟5和步驟10 將結果從promise
發送回generator。
「co」
這樣使用
「next(<someval>)」
的
lib
的逐步解釋
好的,讓咱們繼續async / await
。
正如以前看到的,Generators
能夠幫助消除「回調地獄」,但須要一些第三方庫co
來實現這一點。可是「回調地獄」是一個很大的問題,ECMAScript委員會決定爲Generator
建立一個包裝器並推出新的關鍵字async/await
。
Generators
和Async / Await
之間的區別是:
await
而不是yield
。await
僅適用於Promises
。Async / Await
使用async function
關鍵字,而不是function*
。因此async/await
基本上是Generators的一個子集,而且有一個新的語法糖。
async
關鍵字告訴JavaScript編譯器以不一樣方式處理該函數。只要到達await
函數中的關鍵字,編譯器就會暫停。它假定表達式await
返回一個promise
並等待,直到promise
被解決或拒絕,而後才進一步移動。
在下面的示例中,getAmount函數正在調用兩個異步函數getUser
和getBankBalance
。咱們能夠在promise中作到這一點,但使用async await
更優雅和簡單。
這是一個很是常見的場景,咱們須要在循環中調用異步函數。所以,在ES2018(已完成的提案)中,TC39委員會提出了一個新的Symbol Symbol.asyncIterator
和一個新的構造,for-await-of
以幫助咱們輕鬆地循環異步函數。
常規Iterator對象和異步迭代器之間的主要區別以下:
next()
方法返回值如{value: ‘some val’, done: false}
iterator.next() //{value: ‘some val’, done: false}
next()
方法返回一個Promise,後來解析成相似的{value: ‘some val’, done: false}
iterator.next().then(({ value, done })=> {//{value: ‘some val’, done: false}}
如下示例顯示了for-await-of
工做原理以及如何使用它。
Symbol - 提供全局惟一的數據類型。主要使用它們做爲對象屬性來添加新行爲,所以不會破壞像Object.keys和for-in循環這樣的標準方法。
衆所周知的Symbols- 由JavaScript自動生成的Symbols,可用於在咱們的自定義對象中實現核心方法
Iterables- 是存儲數據集合並遵循特定規則的任何對象,以便咱們可使用標準for-of循環和...擴展運算符從中提取數據。
Iterators- 由Iterables返回並具備next方法它其實是從Iterables中提取數據。
Generator -爲Iterables提供更高級別的抽象。它們還提供了新的控制流,能夠解決諸如回調地獄之類的問題,併爲諸如此類的事物提供構建塊Async/Await。
Async/Await- 爲generator提供更高級別的抽象,以便專門解決回調地獄問題。
Async迭代器- 一種全新的2018功能,可幫助循環異步函數數組,以得到每一個異步函數的結果,就像在普通循環中同樣。