1. 程式人生 > >迭代器 (Iterator) 和 生成器 (Generator)

迭代器 (Iterator) 和 生成器 (Generator)

>其他章節請看: > >[es6 快速入門 系列][1] ## 迭代器 (Iterator) 和 生成器 (Generator) ### 試圖解決的問題 ```javascript let colors = ['red', 'blue', 'green', 'yellow'] for(let i = 0, len = colors.length; i < len; i++){ console.log(colors[i]) } ``` 上面是一段標準的 for 迴圈程式碼,變過變數 i 來跟蹤 colors 的索引,雖然語法簡單,但如果將多個迴圈巢狀則需要跟蹤多個變數,程式碼複雜度會大增,一不小心就錯誤的使用了其他 for 迴圈的跟蹤變數,導致程式出錯。**迭代器**的出現旨在消除這種複雜性並減少迴圈中的錯誤 ### 解決的方法 使用迭代器優化上述問題,請看程式碼: ```javascript let colors = ['red', 'blue', 'green', 'yellow'] for(let color of colors){ console.log(color) // 依次輸出:red blue green yellow } ``` 每個集合(陣列、Map和Set)型別都有一個預設的**迭代器**,在 for-of 迴圈中,如果沒有顯示指定則使用預設迭代器 >關於迭代器是什麼,生成器是什麼,這裡的陣列、for-of和迭代器三者之間的關係又是什麼, > >所有這些疑問都能在下面的**補充**章節中找到解答 ### 補充 #### 什麼是迭代器 下面我們用 es5 **建立**一個迭代器: ```javascript function createIterator(items){ let i = 0; let len = items.length; return { next: function(){ const result = { value: items[i], done: i++ >= len } return result; } } } let iterator = createIterator(['a', 'b', 'c']) console.log(iterator.next()) // { value: 'a', done: false } console.log(iterator.next()) // { value: 'b', done: false } console.log(iterator.next()) // { value: 'c', done: true } console.log(iterator.next()) // { value: undefined, done: true } ``` **迭代器**是一個特殊的物件,它有專門為迭代過程而設計的專有介面,所有的迭代器都有 **next()** 方法,每次呼叫 next() 都返回一個結果物件。結果物件有兩個屬性:一個是value,表示下一個將要返回的值;另一個是done,是一個布林值型別的值,當沒有更多返回資料時返回true 上面這個示例很複雜,es6 中迭代器的編寫規則也很複雜,所以 es6 引入**生成器**,讓建立迭代器的過程變得簡單 #### 什麼是生成器 **生成器**是一種返回迭代器的函式。通過 function 關鍵字後面的星號(*)來表示,函式中會用到新的關鍵字 **yield**。 下面我們用生成器重寫上面的 createIterator() 方法: ```javascript // 星號* 可以緊挨著 function 關鍵字,也可以在中間加空格 function *createIterator(items){ for(let i = 0, len = items.length; i < len; i++){ yield items[i] } } let iterator = createIterator(['a', 'b', 'c']) console.log(iterator.next()) // { value: 'a', done: false } console.log(iterator.next()) // { value: 'b', done: false } console.log(iterator.next()) // { value: 'c', done: false } console.log(iterator.next()) // { value: undefined, done: true } ``` createIterator()前面的星號表明它是一個生成器;yield 是 es6 中的新特性,可以通過它來指定呼叫迭代器的 next() 方法時的返回值和返回順序。 生成器最有趣的大概是,每次執行完 yield 語句後,函式會**自動停止**執行。 yield只能在生成器**內部**使用。下面例子中,從字面上看,yield 確實是在生成器內部,但是它與 return 一樣,二者都不能穿透函式邊界。 ```javascript function *createIterator(items){ items.forEach(function(){ yield 1 // 報錯 }) } ``` **注**:不能用箭頭函式建立生成器 #### 可迭代物件和 for-of 迴圈 首先來回答上面遺留的問題:陣列、for-of 和迭代器三者之間的關係是什麼? ```javascript let colors = ['red', 'blue', 'green', 'yellow'] for(let color of colors){ console.log(color) // 依次輸出:red blue green yellow } ``` es6中,所有的集合(陣列、Map及Set)和字串都是可迭代物件。一個物件如果有與之對應的迭代器,那麼它就是**可迭代物件**;for-of 迴圈每執行一次都會呼叫可迭代物件的 next() 方法,並將迭代器返回的結果物件中的 value 儲存在一個變數中,迴圈將持續到返回物件的 done 屬性的值為 true。 三者之間的關係這就很清楚了,陣列有自己的迭代器,for-of 會迴圈迭代器 es6中的可迭代物件具有 **Symbol.iterator** 屬性,執行 Symbol.iterator 指定的函式會返回一個作用於該物件的迭代器,以陣列為例: ```javascript let arr = [11, 22] let iterator = arr[Symbol.iterator]() console.log(iterator.next()) // { value: 11, done: false } console.log(iterator.next()) // { value: 22, done: false } console.log(iterator.next()) // { value: undefined, done: true } ``` *注*:如果將 for-of 語句用於不可迭代物件、null 或 undefined 將會導致程式報錯 #### 內建迭代器 為了更好的訪問資料,es6給陣列、Set和Map都內建了以下三種迭代器: 1. **entries()** 返回一種迭代器,其值為多個鍵值對 1. **values()** 返回一種迭代器,其值為集合的所有值 1. **keys()** 返回一種迭代器,其值為集合的所有鍵名 ```javascript let arr = [11, 22] for(let entry of arr.entries()){ // 依次輸出:[ 0, 11 ] [ 1, 22 ] console.log(entry) } for(let key of arr.keys()){ // 依次輸出:0 1 console.log(key) } ``` 每種集合都有一個**預設的迭代器**,在 for-of 迴圈中,如果沒有顯示指定則使用預設的迭代器。陣列和 Set 的預設迭代器是 values() 方法,Map 的預設迭代器是 entries() 方法 ```javascript let arr = [11, 22] for(let value of arr.values()){ // 依次輸出:11 22 console.log(value) } // 等同於 let arr = [11, 22] for(let value of arr){ // 依次輸出:11 22 console.log(value) } ``` 自從 es6 添加了迭代器,DOM 中的 **NodeList** 型別也擁有的預設迭代器,其行為與陣列的預設迭代器完全一致 >其他章節請看: > >[es6 快速入門 系列][1] [1]: https://www.cnblogs.com/pengjiali/p/14449150.html 'es6 快速入門