前言
正文
1 、迭代器的產生、定義和模擬
(1) for 迴圈的弊端
普通的for迴圈的弊端因為陣列有已知的長度,且陣列每一項都可以通過索引獲取,所以整個陣列可以通過遞增索引來遍歷。由於如下原因,通過這種迴圈來執行例程並不理想。
a、 迭代之前需要事先知道如何使用資料結構。陣列中的每一項都只能先通過引用取得陣列物件,然後再通過 [] 操作符取得特定索引位置上的項。這種情況並不適用於所有資料結構。
b、 遍歷順序並不是資料結構固有的。通過遞增索引來訪問資料是特定於陣列型別的方式,並不適用於其他具有隱式順序的資料結構。
(2) 迭代器的產生
迭代器是被設計專用於迭代的物件,帶有特定的介面,迭代器持有一個指向集合位置的內部指標,每當呼叫了next()方法,迭代器就會返回一個結果 IteratorResult 物件,該結果物件有兩個屬性,對應下一個值的 value 以及一個布林型別的 done,在最後一個值再呼叫next()則返回 done 屬性值為 true(標識沒有更多值供使用),並且value屬性會是迭代器自身的返回值. 該返回值不是原始資料集的一部分,卻成為相關資料的最後一部分,或在迭代器未提供返回值的時候使用undefined ,迭代器的自身返回值類似於函式的返回值,是向呼叫者返回資訊的最後手段。
(3) 迭代器的模擬
根據上面的定義,手動實現迭代器函式
// createIterator 函式返回一個帶有next()方法的物件,作為迭代器,每次呼叫next()方法,返回具體的IteratorResult 物件值 function createIterator(items) { var i = 0; return { next() { var done = (i >= items.length); var value = !done ? items[i++] : undefined; return { done, value } } } } var iterator = createIterator([11, 22, 33]) console.log(iterator.next());//{done: false, value: 11} console.log(iterator.next());//{done: false, value: 22} console.log(iterator.next());//{done: false, value: 33} console.log(iterator.next());//{done: true, value: undefined}
2、 迭代器模式和可迭代物件
迭代器模式(特別是在 ECMAScript 這個語境下)描述了一個方案,即可以把有些結構稱為“可迭代物件”(iterable),因為它們實現了正式的 Iterable 介面,而且可以通過迭代器 Iterator 消費。
(1)訪問預設迭代器 symbol.iterator
let values = [1, 2, 3, 4] let iterator = values[Symbol.iterator]() console.log(iterator.next());//{value:1,deno:false} console.log(iterator.next());//{value:2,deno:false} console.log(iterator.next());//{value:3,deno:false} console.log(iterator.next());//{value:4,deno:false} console.log(iterator.next());//{value:undefiend,deno:true}
(2) 檢測一個物件是否可以用來迭代
let num = 1; let obj = {}; // 這兩種型別沒有實現迭代器工廠函式 console.log(num[Symbol.iterator]); // undefined console.log(obj[Symbol.iterator]); // undefined let str = 'abc'; let arr = ['a', 'b', 'c']; let map = new Map().set('a', 1).set('b', 2).set('c', 3); let set = new Set().add('a').add('b').add('c'); let els = document.querySelectorAll('div'); // 這些型別都實現了迭代器工廠函式 console.log(str[Symbol.iterator]); // f values() { [native code] } console.log(arr[Symbol.iterator]); // f values() { [native code] } console.log(map[Symbol.iterator]); // f values() { [native code] } console.log(set[Symbol.iterator]); // f values() { [native code] } console.log(els[Symbol.iterator]); // f values() { [native code] } // 呼叫這個工廠函式會生成一個迭代器 console.log(str[Symbol.iterator]()); // StringIterator {} console.log(arr[Symbol.iterator]()); // ArrayIterator {} console.log(map[Symbol.iterator]()); // MapIterator {} console.log(set[Symbol.iterator]()); // SetIterator {} console.log(els[Symbol.iterator]()); // ArrayIterator {}
總結:檢測一個物件是否可以用來迭代的方式:typeof object[Symbol.iterator] === "function",同時可以得出很多內建型別都實現了 Iterable 介面:字串、 陣列、對映、集合、arguments 物件、NodeList 等 DOM 集合型別等。
注意:
a、 每個迭代器都表示對可迭代物件的一次性有序遍歷。不同迭代器的例項相互之間沒有聯絡,只會獨立地遍歷可迭代物件。
b、迭代器並不與可迭代物件某個時刻的快照繫結,而僅僅是使用遊標來記錄遍歷可迭代物件的歷程。如果可迭代物件在迭代期間被修改了,那麼迭代器也會反映相應的變化。如下:
let arr = ['foo', 'baz']; let iter = arr[Symbol.iterator](); let iter2 = arr[Symbol.iterator](); console.log(iter.next()); // { done: false, value: 'foo' } console.log(iter2.next()); // { done: false, value: 'foo' } // 在陣列中間插入值 arr.splice(1, 0, 'bar'); console.log(iter.next()); // { done: false, value: 'bar' } console.log(iter.next()); // { done: false, value: 'baz' } console.log(iter.next()); // { done: true, value: undefined }
3 、開發中使用的迭代器
(1) 集合的內建迭代器
/* entries()迭代器會在每次next()被呼叫時返回一個雙項陣列,此陣列代表了集合中每個元素的鍵與值, 對於陣列來說,第一項是數值索引, 對於set,第一項也是值,因為它的值也會被視為鍵, 對於map來說,第一項就是鍵 */ let arr = [1, 2, 3] console.log(arr.entries());//Array Iterator [] for (const item of arr.entries()) { console.log(item); }// [0:1],[1:2],[2:3] let set = new Set([1, 2, 3]) console.log(set.entries());//SetIterator {[1=>1],[2=>2],[3=>3]} for (const item of set.entries()) { console.log(item); }// [1:1],[2:2],[3:3] let map = new Map() map.set("first", "firseetValue") map.set("second", "seondValue") map.set("third", "thirdValue") console.log(map.entries());//MapIterator {["first"=>"firseetValue"],["second"=>"seondValue"],["third"=>"thirdValue"]} for (const item of map.entries()) { console.log(item); }// ["first":"firseetValue"],["second":"seondValue"],["third":"thirdValue"]
(2)字串的迭代器
// 訪問字串中的字元可以通過下標的形式 let message = "a b" console.log(message[0])// 'a' let info = "a b" for (const c of info) { console.log(c); }//a, ,b
4、自定義函式實現迭代器
// 與 Iterable 介面類似,任何實現 Iterator 介面的物件都可以作為迭代器使用。 class Counter { constructor(limit) { this.limit = limit; } [Symbol.iterator]() { let count = 1,// 把計數器變數放到閉包裡,然後通過閉包返回迭代器,讓一個可迭代物件能夠建立多個迭代器,且每建立一個迭代器就對應一個新計數器 limit = this.limit; return { next() { if (count <= limit) { return { done: false, value: count++ }; } else { return { done: true, value: undefined }; } }, // 可選的 return() 方法用於指定在迭代器提前關閉時執行的邏輯 return() { console.log('Exiting early'); return { done: true }; } }; } } let counter = new Counter(3); for (let i of counter) { console.log(i); } // 1 // 2 // 3 for (let i of counter) { console.log(i); } // 1 // 2 // 3 let counter2 = new Counter(5); try { for (let i of counter2) { if (i > 2) { throw 'err'; } console.log(i); } } catch (e) { } // 1 // 2 // Exiting early
寫在最後
以上就是本文的全部內容,希望給讀者帶來些許的幫助和進步,方便的話點個關注,小白的成長之路會持續更新一些工作中常見的問題和技術點。