[譯] Javascript(ES6)Generator 入門

若是你在過去兩到五年中一直在研究 JavaScript,那麼確定看過關於 GeneratorIterator 的文章。雖然 GeneratorIterator 本質上是相關的,但 Generator 彷佛比 Iterator 更使人難以理解。javascript

IteratorIterable 對象(如 map,數組和字符串等)實現,咱們可以使用 next() 迭代它們。Iterator 在 Generator,Observable 和 Spread 運算符中普遍使用。前端

若是你剛接觸 Iterator,建議先閱讀 Guide to Iteratorsjava

可使用內建的 Symbol.iterator 驗證對象是否符合可迭代要求:android

new Map([[1, 2]])[Symbol.iterator]() // MapIterator {1 => 2}
「hi」[Symbol.iterator]() // StringIterator {}
[‘1’][Symbol.iterator]() // Array Iterator {}
new Set([1, 2])[Symbol.iterator]() // SetIterator {1, 2}
複製代碼

第一次亮相於 ES6 的 Generator 在後續 JavaScript 版本的發佈中並無變化,因此 Generator 有可能在未來會繼續保持如今的特性及用法,咱們是繞不開它的。雖然 ES7 和 ES8 有一些小更新,可是改變幅度沒法與 ES5 到 ES6 相提並論,能夠說 ES6 使得 JavaScript 踏出了新的一步。ios

讀完本文,我相信你必定能充分理解 Generator 的原理。若是你是專業人士,歡迎在回覆中添加評論,一塊兒改進這篇文章。爲幫助你們理解代碼,代碼中已包含必定註釋。git

介紹

衆所周知,JavaScript 的函數都會一直運行到 return 或函數結束。但對於 Generator 函數,會一直運行到 遇到 yield 或 return 或函數結束。與通常函數不一樣,Generator 函數一旦被調用,就會返回一個 Generator 對象。這個對象擁有 Generator Iterable,可使用 next() 方法或 for…of 循環迭代它。es6

Generator 每次調用 next(),函數會一直運行到下一個 yield,而後暫停執行。github

語法上他們的標誌是一個星號 *function* Xfunction *X 的效果相同。後端

Generator 函數返回 Generator 對象。要把 Generator 對象賦值到一個變量,才能方便地使用它的 next() 方法。 若是沒有把 Generator 分配給變量,對它調用 next() 老是隻會運行到第一個 yield 表達式。api

Generator 函數中一般含有 yield 表達式。Generator 函數內的每一個 yield 都是下一個執行循環開始以前的中止點。每一個執行週期都經過 Generator 的 next() 方法觸發。

每次調用 next()yield 表達式都會返回包含如下參數的對象。

{ value: 10, done: false } // 假設 yield 的值是 10

  • Value —— yield 關鍵字右側的值,能夠是對函數的調用、對象等幾乎任何東西。對於空的 yield,返回的是 undefined
  • Done —— 代表 Generator 的狀態,是否能夠繼續執行。完成時返回 true,意味着函數已經運行完畢。

(若是你沒法理解上面說的是什麼,那下面的例子可能會讓你理解得更清晰……)

Generator 函數基礎

**注意:**在上面的例子中,直接訪問 Generator 函數老是執行到第一個 yield。所以,你須要將 Generator 分配給變量才能正確迭代它。

Generator 函數的生命週期

在深刻理解以前,讓咱們快速瀏覽一下 Generator 函數的生命週期示意圖:

Generator 函數的生命週期

每次運行到 yield,Generator 函數都會返回一個對象,該對象包含 yield 產生的值和當前 Generator 函數的狀態。相似地,運行到 return,能夠獲得 return 的值,而且 done 的狀態爲 true。當 done 的狀態爲 true 時,意味着 Generator 函數已經運行完畢,後面的 yield 通通無效。

return 後的一切代碼都會被忽略,包括 yield 表達式。

繼續閱讀深刻理解上圖。

把 yield 賦值到一個變量

在的示例中,咱們建立了一個帶有 yield 的最基本的 Generator,並得到了預期的輸出。在下面代碼中,咱們將整個 yield 表達式賦值到一個變量。

把 yield 賦值到一個變量

把整個 yield 表達式傳到變量的結果是什麼?Undefined …

爲何會是 undefined?從第二個 next() 開始,前一個 yield 會被替換爲 next 函數的參數。由於例子中的 next 沒有傳入任何值,因此程序斷定「前一個 yield 表達式」爲 undefined

這是重點中的重點,下面的章節咱們將詳細介紹對 next() 傳參的用法。

將參數傳遞給 next() 方法

參考上面的示意圖,咱們聊聊關於傳參到 next 函數的事情。這是整個 Generator 使用中最棘手的部分之一

思考如下代碼,其中 yield 被賦給變量,但此次咱們向 next() 傳參。

看看控制檯的輸出,先思考一下,後面會有解釋。

將參數傳遞給 next()

說明:

  1. 在調用 next(20) 的時候,第一個 yield 前的代碼都被執行。由於前面已經沒有 yield,傳入的 20 毫無做用。輸出 yield 的 value 爲 i*10,也就是 100。由於執行到第一個 yield 中止,因此 const j 未被賦值。
  2. 調用 next(10) 時,第一個 yield 的位置被替換爲 10,至關於在返回第二個 yield 的 value 前,設置 yield (i * 10) = 10,因此 j 爲 50。yield 的 value 爲 2 * 50 / 4 = 25
  3. next(5) 用 5 替換第二個 yield,因此 k 爲 5。繼續執行 return 語句,返回最後的 yield value (x + y + z) => (10 + 50 + 5) = 65,而且 done 爲 true。

這可能對初次接觸 Generator 的讀者有點超綱,可是給本身 5 分鐘,多讀幾遍,就能清楚明白。

Yield 做爲其餘函數的參數

Yield 在 Generator 中還有大把的用法,咱們接着看看下面的代碼,這是 yield 的其中一個妙用,附帶解釋。

Yield 做爲其餘函數的參數

解釋

  1. 第一個 next() yield(生成) 的 value 爲 undefined,由於 yield 表達式無值。
  2. 第二個 next() 生成的 value 爲被傳入的 'I am usless',這一步爲函數調用準備了參數。
  3. 第二個 next() 以 undefined 爲參數調用了後面的函數。next() 沒有接收參數,意味着上一個 yield 表達式的值爲 undefined,因此函數打印出 undefined 並終止運行。

對函數調用使用 yield

除了返回普通的值,yield 還能夠調用函數並返回他的值。看看下面的例子更好理解:

對函數調用使用 yield

上述代碼返回了函數返回的對象做爲 yield 的 value,而後把 const user 賦值爲 undefined,結束運行。

對 Promise 使用 yield

對 promise 使用 yield 與對函數調用使用 yield 類似,它會返回一個 promise,咱們以此進一步斷定操做成功或失敗。看看如下代碼,瞭解它的使用方法:

對 Promise 使用 yield

apiCall 將 promise 做爲 yield value 返回,在 2 秒後 resolved 並打印出咱們須要的值。

Yield*

Yield 表達式的介紹就告一段落了,接着咱們瞭解一下另外一個表達式 yield*Yield* 在 Generator 函數中使用時,會把迭代委託到下一個 Generator 函數。簡單來講,會先同步完成 Yield* 表達式中的 Generator 函數,再繼續運行外層函數。

讓咱們看看下面的代碼和解釋,以便更好地理解。此代碼來自 MDN Web 文檔。

Yield* 基礎

解釋

  1. 調用第一個 next(),產生的值爲1。
  2. 第二個 next() 調用的是 yield* 表達式,這意味着咱們要先完成 yield* 表達式指定的 Generator 函數,再繼續運行當前 Generator 函數。
  3. 你能夠假設上面的代碼被替換爲以下代碼:
function* g2() {
  yield 1;
  yield 2;
  yield 3;
  yield 4;
  yield 5;
}
複製代碼

Generator 會按這個順序運行結束。不過對於 yield* 和 return 的同時使用,咱們須要特別注意,下一節將會提到。

Yield* 與 Return

帶 return 的 yield* 與通常 yield* 有點不一樣。當 yield* 與 return 語句一塊兒使用時,yield* 被賦 return 的值,也就是整個 yield* function() 與其關聯 Generator 函數的返回值相等。

讓咱們看看下面的代碼和解釋,以便更好理解。

Yield* 與 Return

說明

  1. 第一個 next(),直接進入 yield 1 並返回其值。
  2. 第二個 next() 返回 2。
  3. 第三個 next(),運行 return 'foo' 後緊接着,yield 返回 'the end',其中 'foo' 被賦值到 const result
  4. 最後一個 next() 結束運行。

對內建 Iterable 對象使用 Yield*

yield* 還有一個值得一提的用法,它能夠遍歷 iterable 對象,如 Array,String 和 Map。

一塊兒看看實際運行結果。

對內建 Iterable 對象使用 Yield*

在代碼中,yield* 遍歷傳入的每個 iterable 對象,我以爲這段代碼自己是不言自明的。

最佳實踐

最重要的是,每一個 iterator/Generator 均可以使用 for…of 遍歷。與顯式調用的 next() 相似,for…of 循環依據 yield 關鍵字 進入下一次迭代。這裏是重點:它只會迭代到最後一個 yield,不會像 next() 那樣處理 return 語句。

下面的代碼能夠驗證以上描述。

Yield 與 for…of

最後 return 的值不會被打印,由於 for…of 循環只迭代到最後一個 yield。所以,做爲最佳實踐,儘可能避免在 Generator 函數中使用 return 語句,緣由在於當使用 for…of 語句進行迭代時,return 會影響函數的可重用性。

總結

我但願這涵蓋了 Generator 函數的基本用法,但願這篇文章能讓你更好地理解 Generator 在 JavaScript 中的工做方式。若是你喜歡本文,請點個贊吧 :)。

請關注個人 GitHub 帳號獲取更多 JavaScript 和全棧項目:

若是發現譯文存在錯誤或其餘須要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可得到相應獎勵積分。文章開頭的 本文永久連接 即爲本文在 GitHub 上的 MarkDown 連接。


掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章
相關標籤/搜索