切圖崽的自我修養-[ES6] 生成器Generator淺析

大切圖崽發表於2019-02-16

Generator

搞這麼神祕 其實就是個迭代器

Generator的核心實際上就是一個Iterator,通過yield關鍵字能夠把函式體拆成完全可控執行片段,在函式體外部通過next來對這些執行片段進行遍歷. 這和遍歷array,set,map這些資料結構是一個道理.只不過generator用來遍歷函式片段,而array/set/map 用來遍歷元素.

  1. 對生成器執行next()操作,進行生成器的入口開始執行程式碼

  2. 執行到第一個yield時,向呼叫者返回一個值,並將函式掛起;

  3. 掛起時,函式當前的執行上下文環境和引數被儲存下來;

  4. 執行到第二個yield時,引數從掛起狀態被重新呼叫,進入上次掛起的執行上下文環境繼續下面的操作,到下一個yield操作時重複上面的過程


執行過程解析

    function *generator() {
      console.log(1);
      var x = (yield 2) +3
      console.log(3);
        var y = yield 4;
    }

    var g = generator(); 
    console.log(g.next());
    console.log(g.next());
    console.log(g.next());
    console.log(g.next());
    
  1. var g = generator() 這一句毛都不會輸出,實際上這句話執行完之後,generator()形成了記憶體洩漏狀態,因為函式體內部的物件被外部全域性變數(g)所引用,導致generator()無法被回收. 這一句執行完後實際上 g = {next:function(){xxxxxx}},

  2. 第一個g.next()會執行console.log(1),向下執行到yield關鍵字,將{value:2,done:false}返回給外部,然後掛起當前函式

  3. 第二個g.next()會執行 (yield 2)+3 的操作,會執行把 (yield 2)+3 賦值給x的操作, 會執行console.log(3)的操作,向下執行到yield關鍵字,把{value:4,done:false}返回給外部,掛起當前函式.

  4. 第三個g.next()會執行把(yield 4)賦值給y的操作,向下掃描執行,沒有發現有yield或者return了,這時候會throw一個stopIterator的異常,表示在這之後已經沒有yield或者return語句可以迭代了,於是把done值置為true. 返回{value:undefined,done:true}

  5. 第四個g.next()同理,返回{value:undefined,done:true}


    function *generator(z) {
      console.log(1);
      var x = (yield 2) +z
      console.log(x);
      console.log(3);
        var y = yield 4;
        console.log(y)
    }

    var g = generator(5); 
    console.log(g.next(7)); 
    console.log(g.next(8));
    console.log(g.next(9));
    console.log(g.next(10));
    
  1. var g = generator(5)這一句毛都不輸出,實際上這句話執行完之後,generator()形成了記憶體洩漏狀態,因為函式體內部的物件被外部全域性變數(g)所引用,導致generator()無法被回收. 這一句執行完後實際上 g = {next:function(){xxxxxx}}, 且函式體內的z引數被初始化成5.

  2. 第一個g.next(7)進入函式,會執行console.log(1),向下執行掃描到了yield關鍵字,於是將{value:2,done:false}返回給外部,然後掛起當前函式

  3. 第二個g.next(8)從上次掛起的地方進入函式,會執行 把next的引數8賦值給(yield 2)的操作, 會執行 (yield 2)+z 的操作, 會執行 把(yield 2)+z的結果賦值給x的操作, 會執行console.log(x)的操作,會執行console.log(3)的操作,繼續向下執行掃描到了yield關鍵字,於是將{value:4,done:false}返回給外部,然後掛起當前函式

  4. 第三個g.next(9)從上次掛起的地方進入函式,會執行把next的引數9賦值給(yield 4)操作,會執行把(yield 4)賦值給y的操作,會執行console.log(y)的操作. 然後把向下執行掃描,沒有發現有yield或者return了,這時候會throw一個stopIterator的異常,表示在這之後已經沒有yield或者return語句可以迭代了,於是把done值置為true. 返回{value:undefined,done:true},然後掛起當前函式

  5. 第四個g.next(10)從上次掛起的地方進入函式,也是同理,返回{value:undefined,done:true},然後掛起當前函式


相關參考

由於Iterator/Generator無一例外的都涉及到了javascript函式從建立到執行到銷燬的過程, 涉及到了 執行上下文EC/活動物件AO/變數物件VO/記憶體洩漏/回收機制 等核心概念,東西多且較複雜,有興趣的同學可以參考湯姆大叔深入理解JavasSript系列, 裡面有對javascript的核心(函式/原型鏈)等做了詳盡的介紹.

相關文章