for of
提及 for of
相信每一個寫過 JavaScript
的人都用過 for of
,平時咱們用它作什麼呢?大多數狀況應該就是遍歷數組了,固然,更多時候,咱們也會用 map()
或者 filer()
來遍歷一個數組。 可是就像咱們標題裏面說的,它跟 Generator
能扯上什麼關係呢?javascript
首先咱們想一個問題,爲何使用 for of
或者 map()
/filer()
方法就能夠遍歷一個數組 (或者類數組對象: Strings
, Maps
, Sets
, arguments
) 呢? 爲何不能用他們來遍歷一個對象呢?前端
for of
更深刻的理解iterator
究竟是何方神聖?for of
來遍歷對象呢?for of
?Generator
又是何方神聖?他有什麼用呢?在真正揭開謎底以前,站在 for of
的角度想一下,如今讓你去遍歷一個數組,你須要知道什麼信息呢?java
帶着這樣的思考,咱們打印一個數組來看看這裏面的玄機:數組
const numbersArray = [1, 2, 3]; console.dir(numbersArray);
數組 (或者類數組對象: Strings
, Maps
, Sets
, arguments
) 的原型中都實現了一個方法 Symbol.iterator
,問題來了,那麼這個 Symbol.iterator
又有什麼用呢? 拿出來試一下就知道了:數據結構
let iterator = numbersArray[Symbol.iterator](); // 咱們把這個 Symbol.iterator 打印一下看看裏面到底有些什麼 console.dir(iterator);
這裏有一個 next()
方法對嗎?執行這個 next()
方法:異步
iterator.next(); // 輸出 {value: 1, done: false} iterator.next(); // 輸出 {value: 2, done: false} iterator.next(); // 輸出 {value: 3, done: false} iterator.next(); // 輸出 {value: undefined, done: true}
請注意,當下標超出時,value: undefined
咱們發現這個 iterator.next()
每次都返回了一個對象。這對象包含兩個信息:當前下標的值,以及遍歷是否結束的標誌。這印證了咱們以前思考,有了這兩個信息,你做爲 for of
函數,也能打印出數組的每一項了不是嗎?ide
新的問題來了,iterator
究竟是何方神聖呢?函數
iterator
(迭代器) & The iterator protocol
(迭代協議)聊到了 iterator
咱們不得不先說一下 The iterator protocol
(迭代協議)post
" The iterable protocol allows JavaScript objects to define or customize their iteration behavior " - MDN
MDN 上是這麼說的:The iterator protocol
容許 JavaScript
對象去定義或定製它們的迭代行爲 ,因此上面出現的 Symbol.iterator
這個方法,就是數組對於這個協議的實現。那麼按照這個協議,數組是怎麼實現了一個 iterator
呢?ui
"In JavaScript an iterator is an object which defines a sequence and potentially a return value upon its termination. More specifically an iterator is any object which implements the Iterator protocol by having a next() method which returns an object with two properties: value, the next value in the sequence; and done, which is true if the last value in the sequence has already been consumed. If value is present alongside done, it is the iterator's return value." - MDN
這一大段看起來比較費勁,簡單來講就像咱們上一章節所印證的,它實現的方式是定義了一個 next()
方法,而這個 next()
方法每次被執行都會返回一個對象: {value:xxx/undefined , done: true/false }
其中 value
表明的是當前遍歷到的值,done
表明是否遍歷結束。
本小節回答了咱們以前的提問: 爲何不能用 for of
來遍歷一個對象呢? 緣由很簡單:JavaScript
的對象中沒有實現一個這樣的 iterator
。你能夠打印一個對象來看看結果如何:
console.dir({ a: 1, b: 2 });
okay, 到這裏若是就結束的話,那咱們瞭解得還不夠深刻,因而再問一個問題:
Why is there no built-in object iteration ? (爲何在 object
中沒有內置迭代器呢? )
object
中沒有內置迭代器呢?對啊,爲何呢? 咱們在各樣的場景中也須要來遍歷一個對象啊?爲何沒有內置一個迭代器呢?要回答這個問題,咱們得從另一個角度出發,瞭解一些基本的概念:
咱們經常說遍歷對象,可是簡單來講,只會在兩種層級上來對一個 JavaScript
對象進行遍歷:
Array.prototype.length
這個屬性與對象的結構相關,但卻不是它的數據。array = [a, b, c, d]
那麼迭代器訪問到的是 1, 2, 3, 4
。明白了這個原因,JavaScript
雖然不支持用 for of
來遍歷對象,可是提供了一個 for in
方法來遍歷全部非 Symbol
類型而且是可枚舉的屬性。
標準不支持,若是咱們就是要用 for-of
來遍歷對象呢?那咱們能夠任性的實現一個啊:
Object.prototype[Symbol.iterator] = function*() { for (const [key, value] of Object.entries(this)) { yield { key, value }; } };
for (const { key, value } of { a: 1, b: 2, c: 3 }) { console.log(key, value); }
不知道你有沒有注意一個細節,在咱們任性的實現一個 iterator
的代碼中,咱們用到了一個很奇怪的結構 function*() {}
,這個就是咱們接下來要介紹的 Generator
看到這個名字以爲很厲害哈,但其實很簡單,寫一個 Generator
你只須要在函數名和 function
關鍵字中間加一個 *
號就能夠了。至於裏面的 yield
是什麼,後面會說的。
talk is cheap , show me the code
,用一個例子,簡單說一下概念。
咱們如今定義了一個這樣的 Generator
叫作 gen
function* gen() { yield 1; yield 2; yield 3; yield 4; }
咱們只能看到,這裏面有 4 個語句,那打印一下看看唄:
這裏發現了一個熟悉的函數,next()
方法,咱們把 gen
實例化一下,執行一下它的 next()
來看看結果:
仍是熟悉的味道,那麼到這裏,咱們已經知道,Generator
能夠實例化出一個 iterator
,而且這個 yield
語句就是用來中斷代碼的執行的,也就是說,配合 next()
方法,每次只會執行一個 yield
語句。
多說一句,針對 Generator
自己,還有一個有意思的特性,yield
後面能夠跟上另外一個 Generator
而且他們會按照次序執行:
function* gen() { yield 1; yield* gen2(); return; } function* gen2() { yield 4; yield 5; } let iterator = gen(); console.log(iterator.next()); console.log(iterator.next()); console.log(iterator.next()); console.log(iterator.next());
結果頗有意思不是嗎?並且 return
會終結整個 Generator
,換句話說:寫在 return
後面的 yield
不會執行。
Generator
有什麼用? 聰明的同窗可能已經猜到了,是的,它可以中斷執行代碼的特性,能夠幫助咱們來控制異步代碼的執行順序:
例若有兩個異步的函數 A
和 B
, 而且 B
的參數是 A
的返回值,也就是說,若是 A
沒有執行結束,咱們不能執行 B
。
那這時候咱們寫一段僞代碼:
function* effect() { const { param } = yield A(); const { result } = yield B(param); console.table(result); }
這時候咱們若是須要獲得 result
那麼咱們就須要:
const iterator = effect() iterator.next() iterator.next()
執行兩次 next()
獲得結果,看起來很傻不是嗎?有沒有好的辦法呢?(廢話,確定有啊)
假設你在每次執行 A()
/ B()
的請求結束以後,都會自動執行 next()
方法呢?這不就解決了嗎?
這樣的庫早就存在了,建議你們參考 co
的源碼,固然你也能夠經過閱讀 這篇文章 來看看,到底 Generator
是怎麼玩的。
這裏是 Dendoink ,奇舞週刊原創做者,掘金 [聯合編輯 / 小冊做者] 。
對於技術人而言:技 是單兵做戰能力,術 則是運用能力的方法。駕輕就熟,出神入化就是 藝 。在前端娛樂圈,我想成爲一名出色的人民藝術家。掃碼關注公衆號 前端惡霸 我在這裏等你: