天天看點

JS魔法堂:ES6新特性——GeneratorFunction介紹

一、前言                                

  第一次看koajs的示例時,發現該語句 function *(next){...............} ,這是啥啊?于是搜尋一下,原來這是就是es6的新特性generator function(生成器函數)。

  那什麼是生成器函數呢?其實就相當于c#2.0中通過yield關鍵字實作的疊代器的生成器(細節有所不同),那麼了解的關鍵就在疊代器和yield關鍵字兩部分了。下面将嘗試從表象出發,逐漸對生成器函數及利用它進行異步程式設計進行淺層的分析了解。

二、表象——文法及基本使用                     

  示例:

  1. 生成器語函數定義

  普通函數添加*号後則成為了成為了生成器函數了。

  生成器函數的行為與普通函數并不相同,表現為如下3點:

  1. 通過new運算符或函數調用的形式調用生成器函數,均會傳回一個生成器執行個體;

  2. 通過new運算符或函數調用的形式調用生成器函數,均不會馬上執行函數體的代碼;

  3. 必須調用生成器執行個體的next方法才會執行生成器函數體的代碼。

  2、 關鍵字yield——疊代器生成器

   用于馬上退出代碼塊并保留現場,當執行疊代器的next函數時,則能從退出點恢複現場并繼續執行下去。下面有2點需要注意:

    1. yield後面的表達式将作為疊代器next函數的傳回值;

    2. 疊代器next函數的入參将作為yield的傳回值(有點像運算符)。

  3、疊代器(generator)

    疊代器是一個擁有 {value:{*}, done:{boolean}} next([*])方法 和 {undefined} throw([*])方法 的對象,通過next函數不斷執行以關鍵字yield分割的代碼段,通過throw函數令yield分割的代碼段抛出異常。

三、核心1——疊代器                      

疊代器更多的是指疊代器模式,疊代器模式是指通過一個名為疊代器的對象按一定的規則周遊集合元素,調用者隻需告訴疊代器擷取下一個元素即可,而集合的類

型、如何擷取元素等因素均由具體的疊代器自行處理。(又一次地關注點分離!)并且由于疊代器模式可以做到 按需執行/延遲執行

的效果,是以能降低周遊無限序列時記憶體/棧溢出的問題,也能作為異步程式設計模式使用。

  模式了解的注意點:

      1. 疊代器每次進通路集合的一個元素,并由調用者發起通路請求時疊代器才執行下一次通路操作

      2. “按一定的規則”,意味着不一定周遊集合中所有的元素,并且規則可以内聚到疊代器的具體實作上,也可通過政策模式外移到其他子產品中;

      3. “集合”,集合可以是一開始就已經初始化好的有限序列集合(如[1,2,3,4,5,6,7]),也可以是按需生成的無限序列集合(如1到無限大)

      4. “集合元素”,可以是整數集合、字元串集合等資料集合,也可以是函數等指令+資料的集合;

若觸過c#、java等服務端語句的朋友應該對疊代器有一定程度的了解,c#的ienumrable、ienumerator和java的

iterable、iterator就是跟疊代器相關的接口定義,繼承上述接口的疊代器實作均可以通過foreach或for...in語句作循環操作。

  那麼這裡有2點是要注意的:

      1. 疊代器是指設計模式,跟具體的語言無關,是以所有語言均可根據該模式實作具體的疊代器;

      2. foreach或for...in語句是文法層面的支援,跟疊代器模式沒有必然聯系。(若文法層面不支援,那函數式程式設計中的遞歸的效果是一樣的,假如編譯器/解析器支援尾遞歸則更好了,可以js不支援)

  下面我們通過疊代器來實作python中的range函數,并通過range函數建立一個超大的有限序列正整數集合(直接用數組的話絕有可能導緻棧溢出哦!)。

  由于js是單線程運作,并且當ui線程被阻塞n秒後,浏覽器會詢問是否停止腳本的執行,但上述代碼并不會由于序列過大造成棧溢出的問題。假如預先生成1到99999999999999999999或更大數字的數組,那很有可能造成stack overflow。那是由于疊代器實質為一狀态機,而調用next函數則是觸發狀态的轉換,而狀态機中同一時刻用于存放變量的存儲空間固定,并不會出現無限增長的情況。

四、核心2——yield關鍵字                   

回到關鍵字yield上了,其實yield關鍵字就是以一種更直覺、便捷的方式讓我們建立用于周遊有限序列集合的疊代器,而yield則用于将生成器函數

的代碼切片作為有限序列集合的元素(元素的類型為指令+資料,而不僅僅是資料而已)。下面我們一起看看yield關鍵字是怎樣對代碼切片的吧!

  上述代碼最終會被解析為下面的代碼:

 五、異步調用中的應用                       

  由于疊代器模式實作 延遲執行/按需執行,是以可作為一種異步程式設計模式來應用。

  主邏輯中異步調用的寫法與同步調用的基本沒差異了,爽了吧!但異步任務模型與生成器函數及其生成的疊代器耦合性太大,還是不太好用。下面我們通過實作了promises/a+規範的q來進一步解耦。

若執行引擎不支援關鍵字yield,那麼上述代碼不就無法執行了嗎?還是那句話,yield關鍵字其實就是文法糖,最終還是會被解析為一個疊代器。是以我

們自行實作一個疊代器也是能實作上述效果的,不過過程會繁瑣很多(若如第2節的示例那樣存在try...catch語句,就繁瑣死了@~@),并且代碼的

整潔性、可維護性就全靠攻城獅來保證了。(文法糖從文法層面簡化程式設計和維護難度,但了解底層的工作原理也十分重要哦!)

六、與q結合                        

  暫未閱讀q的源代碼,暫不作詳細分析。反正api就這樣用,呵呵!

七、與ipromise結合                    

ipromise是我開發的一個promises/a+的完整實作,閱讀源碼你會發現它繼承了jquery.deferred1.5~2.1、

jsdeferred、mmdeferred和promises/a官網實作示例的精妙設計,并且從v0.0.6開始支援es6特性

generatorfunction。使用示例如下:

八、總結                          

  generator function并不是為異步程式設計而生,但可以将它結合promise來實作良好的異步程式設計模型。本篇内容僅簡單介紹generator function及相關的異步程式設計内容,若有纰漏請各位指正,謝謝!