天天看點

ES6學習筆記17--Generator函數

Generator 函數是一個狀态機,封裝了多個内部狀态。

執行 Generator 函數會傳回一個周遊器對象,可以依次周遊Generator 函數内部的每一個狀态。

形式上,Generator 函數是一個普通函數,但有兩個特征。

(1)function 關鍵字與函數名之間有一個星号(function*)。

(2)函數體内部使用 yield 表達式,定義不同的内部狀态。

Generator 函數調用方法與普通函數一樣,不同的是,調用 Generator 函數後該函數并不執行,傳回的也不是函數運作結果,而是一個指向内部狀态的指針對象。必須調用周遊器對象的 next 方法,使得指針移向下一個狀态傳回目前指向的結果。

調用Generator 函數傳回一個周遊器對象,代表 Generator 函數的内部指針。每次調用周遊器對象的 next 方法,就會傳回一個有着 value 和 done 兩個屬相的對象。value 屬性表示目前的内部狀态的值。done 屬性是一個布爾值,表示是否周遊結束。

Generator 函數執行後,傳回一個周遊器對象,該對象本身也具有 Symbol.iterator  屬性,執行後傳回自身。

  • yield 表達式

yield 表達式就是暫停狀态。yield 表達式隻能用在 Generator  函數裡面,用在其他地方會 報錯。

ES6學習筆記17--Generator函數

注意:yield 表達式如果用在另一個表達式中,必須放在圓括号裡面。

yield 表達式用作函數參數或放在指派表達式的右邊,可以不加括号。

  • Generator 與狀态機

Generator 是實作狀态機的最佳結構。每運作一次,改變一次狀态。

  • Generator 與 協程(coroutine)

協程 是一種程式運作的方式,可以了解成"協作的線程"或"協作的函數"。協程既可以用單線程實作,也可以用多線程實作。

(1)協程與子例程的差異

傳統的"子例程"(subroutine) 采用堆棧式"後進先出"的執行方式,隻有當調用的子函數完全執行完畢,才會結束執行父函數。

協程為多個線程(單線程情況下,即多個函數)可與并行執行,但是隻有一個線程(或函數)處于正在運作的狀态,其他線程(或函數)都處于暫停态(suspended),線程(或函數)之間可以交換執行權。

從實作上來看,在記憶體中,子例程隻使用一個棧(stack),而協程是同時存在多個棧,但隻有一個棧是在運作狀态。

協程式以多占用記憶體為代價,實作多任務的并行。

(2)協程與普通線程的差異

不同:同一時間可以有多個線程處于運作狀态,但是運作的協程隻能有一個,其他協程都處于暫停狀态。

普通的線程是搶先式的,到底哪個線程先得到資源,必須由運作環境決定。但是協程是合作式的,執行權由協程自己配置設定。

由于 JavaScript 是單線程語言,隻能保持一個調用棧。引入協程以後,每個任務可以保持自己的調用棧。這樣做的最大好處,就是抛出錯誤的時候,可以找到原始的調用棧。不至于像異步操作的回調函數那樣,一旦出錯,原始的調用棧早就結束。

Generator 函數是 ES6 對協程的實作,但屬于不完全實作。Generator 函數被稱為“半協程”(semi-coroutine),意思是隻有 Generator 函數的調用者,才能将程式的執行權還給 Generator 函數。如果是完全執行的協程,任何函數都可以讓暫停的協程繼續執行。

如果将 Generator 函數當作協程,完全可以将多個需要互相協作的任務寫成 Generator 函數,它們之間使用

yield

表達式交換控制權。
  • Generator 與上下文

JavaScript 代碼運作時,會産生一個全局的上下文環境(context,又稱運作環境),包含了目前所有的變量和對象。

然後,執行函數(或塊級代碼)的時候,又會在目前上下文環境的上層,産生一個函數運作的上下文,變成目前(active)的上下文,由此形成一個上下文環境的堆棧(context stack)。

這個堆棧是“後進先出”的資料結構,最後産生的上下文環境首先執行完成,退出堆棧,然後再執行完成它下層的上下文,直至所有代碼執行完成,堆棧清空。

Generator 函數不是這樣,它執行産生的上下文環境,一旦遇到

yield

指令,就會暫時退出堆棧,但是并不消失,裡面的所有變量和對象會當機在目前狀态。等到對它執行

next

指令時,這個上下文環境又會重新加入調用棧,當機的變量和對象恢複執行。

  • 應用

(1)異步操作的同步化表達

(2)控制流管理

(3)部署Iterator 接口

(4)作為資料結構

  • for...of 循環

for...of 循環可以自動周遊 Generator 函數運作時生成的 Iterator 對象,且此時不再需要調用 next 方法。

注:一旦 next 方法的傳回對象的done 屬性為 true,for...of 循環就會中止,且不包含該傳回對象。

除了

for...of

循環以外,擴充運算符(

...

)、解構指派和

Array.from

方法内部調用的,都是周遊器接口。這意味着,它們都可以将 Generator 函數傳回的 Iterator 對象,作為參數

  • Generator.prototype.next()

yield 表達式本身是沒有傳回值,或者說總是傳回 undefined。next 方法可以帶一個參數,該參數就會當做上一個 yield 表達式的傳回值。

這個功能有很重要的文法意義。Generator 函數從暫停狀态到恢複運作,它的上下文狀态(context)是不變的。通過

next

方法的參數,就有辦法在 Generator 函數開始運作之後,繼續向函數體内部注入值。也就是說,可以在 Generator 函數運作的不同階段,從外部向内部注入不同的值,進而調整函數行為。
ES6學習筆記17--Generator函數
  • Generator.prototype.throw()

Generator 函數傳回的周遊器對象,都有一個 throw 方法,可以在函數體外抛出錯誤,然後在 Generator 函數體内捕獲。

Generator.prototype.throw() 方法可以接受一個參數,該參數會被 catch 語句接收,建議抛出 Error 對象的執行個體。

(1)若 Generator 函數内部沒有部署 try...catch 代碼塊,那麼 Generator.prototype.throw() 方法抛出的錯誤,将被外部 try...catch 代碼塊捕獲。

(2)若 Generator 函數内部和外部都沒有部署 try...catch ,那麼程式将報錯,直接中斷執行。

(3)Generator.prototype.throw() 方法抛出的錯誤要被内部捕獲,前提是必須至少執行過一次 next 方法。

第一次執行 next() 方法,等用于啟動執行 Generator 函數的内部代碼,否則 Generator 函數還沒有開始執行,這時的 throw方法抛出的異常隻能被函數體外的 catch 捕獲。

(4)throw 方法被捕獲後,會附帶執行下一條 yield 表達式,即附帶執行一次 next方法。隻要 Generator 函數内部部署了 

try...catch 代碼塊,那麼Generator.prototype.throw() 方法抛出的錯誤不影響下一次周遊。

注:不要混淆周遊器對象的

throw

方法(Generator.prototype.throw())和全局的

throw

指令。周遊器對象的

throw

方法抛出的異常 Gnerator 函數體内的 catch 以及 函數體外的 catch 都可以捕獲到(若 Generator 函數内部沒有部署 try...catch),但全局的 throw指令 隻能被函數體外的catch 語句捕獲。

一旦 Generator 執行過程中抛出錯誤,且沒有被内部捕獲,就不會再執行下去了。如果此後還調用

next

方法,将傳回一個

value

屬性等于

undefined

done

屬性等于

true

的對象,即 JavaScript 引擎認為這個 Generator 已經運作結束了。

  • Generator.prototype.return()

Generator.prototype.return() 傳回給定的值,并且終結周遊Generator 函數。

若 return() 方法調用時,不提供參數,則傳回值的 value 屬性為 undefined。

ES6學習筆記17--Generator函數

總結:next()、throw()、return()  作用都是讓 Generator 函數恢複執行,并且使用不用的語句替換 yield 表達式。

next() 是将 yield 表達式替換成一個值

throw() 是将 yield 表達式替換成一個 throw 語句

return() 是将 yield 表達式替換成一個 return 語句

  • yield* 表達式 用來在一個Generator 函數裡面執行另一個 Generator函數

任何資料結構隻要有 Iterator 接口,就可以被 yield* 周遊。

yield*

指令可以很友善地取出嵌套數組的所有成員。

ES6學習筆記17--Generator函數
ES6學習筆記17--Generator函數