萬盛學電腦網

 萬盛學電腦網 >> 腳本專題 >> javascript >> JavaScript中的迭代器和生成器詳解

JavaScript中的迭代器和生成器詳解

 處理集合裡的每一項是一個非常普通的操作,JavaScript提供了許多方法來迭代一個集合,從簡單的for和for each循環到 map(),filter() 和 array comprehensions(數組推導式)。在JavaScript 1.7中,迭代器和生成器在JavaScript核心語法中帶來了新的迭代機制,而且還提供了定制 for…in 和 for each 循環行為的機制。

迭代器

迭代器是一個每次訪問集合序列中一個元素的對象,並跟蹤該序列中迭代的當前位置。在JavaScript中迭代器是一個對象,這個對象提供了一個 next() 方法,next() 方法返回序列中的下一個元素。當序列中所有元素都遍歷完成時,該方法拋出 StopIteration 異常。

迭代器對象一旦被建立,就可以通過顯式的重復調用next(),或者使用JavaScript的 for…in 和 for each 循環隱式調用。

簡單的對對象和數組進行迭代的迭代器可以使用 Iterator() 被創建:

 

代碼如下:
var lang = { name: 'JavaScript', birthYear: 1995 };
    var it = Iterator(lang);

 

一旦初始化完成,next() 方法可以被調用來依次訪問對象的鍵值對:

 

代碼如下:
  var pair = it.next(); //鍵值對是["name", "JavaScript"]
    pair = it.next(); //鍵值對是["birthday", 1995]
    pair = it.next(); //一個 `StopIteration` 異常被拋出

 

for…in 循環可以被用來替換顯式的調用 next() 方法。當 StopIteration 異常被拋出時,循環會自動終止。

 

代碼如下:
 var it = Iterator(lang);
    for (var pair in it)
      print(pair); //每次輸出 it 中的一個 [key, value] 鍵值對

 

如果你只想迭代對象的 key 值,可以往 Iterator() 函數中傳入第二個參數,值為 true:

 

代碼如下:
  var it = Iterator(lang, true);
    for (var key in it)
      print(key); //每次輸出 key 值

 

使用 Iterator() 訪問對象的一個好處是,被添加到 Object.prototype 的自定義屬性不會被包含在序列對象中。

Iterator() 同樣可以被作用在數組上:

 

代碼如下:
var langs = ['JavaScript', 'Python', 'Haskell'];
    var it = Iterator(langs);
    for (var pair in it)
      print(pair); //每次迭代輸出 [index, language] 鍵值對

 

就像遍歷對象一樣,把 true 當做第二個參數傳入遍歷的結果將會是數組索引:

 

 var langs = ['JavaScript', 'Python', 'Haskell'];
    var it = Iterator(langs, true);
    for (var i in it)
      print(i); //輸出 0,然後是 1,然後是 2

 

使用 let 關鍵字可以在循環內部分別分配索引和值給塊變量,還可以解構賦值(Destructuring Assignment):

 

代碼如下:
 var langs = ['JavaScript', 'Python', 'Haskell'];
    var it = Iterators(langs);
    for (let [i, lang] in it)
      print(i + ': ' + lang); //輸出 "0: JavaScript" 等

 

聲明自定義迭代器

一些代表元素集合的對象應該用一種指定的方式來迭代。

1.迭代一個表示范圍(Range)的對象應該一個接一個的返回這個范圍包含的數字
2.一個樹的葉子節點可以使用深度優先或者廣度優先訪問到
3.迭代一個代表數據庫查詢結果的對象應該一行一行的返回,即使整個結果集尚未全部加載到一個單一數組
4.作用在一個無限數學序列(像斐波那契序列)上的迭代器應該在不創建無限長度數據結構的前提下一個接一個的返回結果

JavaScript 允許你寫自定義迭代邏輯的代碼,並把它作用在一個對象上

我們創建一個簡單的 Range 對象,包含低和高兩個值:

 

代碼如下:
function Range(low, high){
      this.low = low;
      this.high = high;
    }

 

現在我們創建一個自定義迭代器,它返回一個包含范圍內所有整數的序列。迭代器接口需要我們提供一個 next() 方法用來返回序列中的下一個元素或者是拋出 StopIteration 異常。

 

代碼如下:
 function RangeIterator(range){
      this.range = range;
      this.current = this.range.low;
    }
    RangeIterator.prototype.next = function(){
      if (this.current > this.range.high)
        throw StopIteration;
      else
        return this.current++;
    };

 

我們的 RangeIterator 通過 range 實例來實例化,同時維持一個 current 屬性來跟蹤當前序列的位置。

最後,為了讓 RangeIterator 可以和 Range 結合起來,我們需要為 Range 添加一個特殊的 __iterator__ 方法。當我們試圖去迭代一個 Range 時,它將被調用,而且應該返回一個實現了迭代邏輯的 RangeIterator 實例。

 

代碼如下:
Range.prototype.__iterator__ = function(){
      return new RangeIterator(this);
    };

 

完成我們的自定義迭代器後,我們就可以迭代一個范圍實例:

 

代碼如下:
var range = new Range(3, 5);
    for (var i in range)
      print(i); //輸出 3,然後 4,然後 5

 

生成器:一種更好的方式來構建迭代器

雖然自定義的迭代器是一種很有用的工具,但是創建它們的時候要仔細規劃,因為需要顯式的維護它們的內部狀態。

生成器提供了很強大的功能:它允許你定義一個包含自有迭代算法的函數, 同時它可以自動維護自己的狀態。

生成器是可以作為迭代器工廠的特殊函數。如果一個函數包含了一個或多個 yield 表達式,那麼就稱它為生成器(譯者注:Node.js 還需要在函數名前加 * 來表示)。

注意:只有 HTML 中被包含在 <script type="application/javascript;version=1.7"> (或者更高版本)中的代碼塊才可以使用 yield 關鍵字。XUL (XML User Interface Language) 腳本標簽不需要指定這個特殊的代碼塊也可以訪問這些特性。

當一個生成器函數被調用時,函數體不會即刻執行,它會返回一個 generator-iterator 對象。每次調用 generator-iterator 的 next() 方法,函數體就會執行到下一個 yield 表達式,然後返回它的結果。當函數結束或者碰到 return 語句,一個 StopIteration 異常會被拋出。

用一個例子來更好的說明:

 

代碼如下:
function simpleGenerator(){
      yield "first";
      yield "second";
      yield "third";
      for (var i = 0; i < 3; i++)
        yield i;
    }
   
    var g = simpleGenerator();
    print(g.next()); //輸出 "first"
    print(g.next()); //輸出 "second"
    print(g.next()); //輸出 "third"
    print(g.next()); //輸出 0
    print(g.next()); //輸出 1
    print(g.next()); //輸出 2
    print(g.next()); //拋出 StopIteration 異常

 

生成器函數可以被一個類直接的當做 __iterator__ 方法使用,在需要自定義迭代器的地方可以有效的減少代碼量。我們使用生成器重寫一下 Range :

代碼如下:
function Range(low, high){
      this.low = low;
      this.high = high;
    }
    Range.prototype.__iterator__ = function(){
      for (var i = this.low; i <= this.high; i++)
        yield i;
    };
    var range = new Range(3, 5);
    for (var
copyright © 萬盛學電腦網 all rights reserved