什么是迭代?
迭代的意思是按照順序反復多次執行一段程序。循環是迭代機制的基礎,因為它可以指定迭代的次數,以及每次迭代要執行的操作。
迭代器模式
迭代器模式描述了一個方案,可以把有些結構稱為“可迭代對象”?,這些對象實現了正式的Iterable接口,而且可以通過迭代器Iterator消費。
迭代器是按需創建的一次性對象,每個迭代器都會關聯一個可迭代對象,而迭代器會暴露迭代其關聯可迭代對象的API。
實現Iterable接口要求同時具備兩種能力:支持迭代的自我識別能力和創建實現Iterator接口的對象的能力。
以下內置類型實現了Iterable接口:
- 字符串
- 數組
- 映射
- 集合
- argumens對象
- NodeList等DOM集合類型
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 {}
接收可迭代對象的原生語言特性包括:
- for-of循環
- 數組解構
- 擴展操作符
- Array.from()
- 創建集合
- 創建映射
- Promise.all()接收由期約組成的可迭代對象
- Promise.race()接收由期約組成的可迭代對象
- yield*操作符,在生成器中使用
let arr = ['foo', 'bar', 'baz']; // for-of 循環 for (let el of arr) { console.log(el); } // foo // bar // baz // 數組解構 let [a, b, c] = arr; console.log(a, b, c); // foo, bar, baz // 擴展操作符 let arr2 = [...arr]; console.log(arr2); // ['foo', 'bar', 'baz'] // Array.from() let arr3 = Array.from(arr); console.log(arr3); // ['foo', 'bar', 'baz'] // Set 構造函數 let set = new Set(arr); console.log(set); // Set(3) {'foo', 'bar', 'baz'} // Map 構造函數 let pairs = arr.map((x, i) => [x, i]); console.log(pairs); // [['foo', 0], ['bar', 1], ['baz', 2]] let map = new Map(pairs); console.log(map); // Map(3) { 'foo'=>0, 'bar'=>1, 'baz'=>2 } 如果對象原型鏈上的父類實現了 Iterable 接口,那這個對象也就實現了這個接口: class FooArray extends Array {} let fooArr = new FooArray('foo', 'bar', 'baz'); for (let el of fooArr) { console.log(el); } // foo // bar // baz
迭代器API使用next()方法在可迭代對象中遍歷數據。next()方法返回的迭代器對象IteratorResult 包含兩個屬性:done 和 value。done 是一個布爾值,表示是否還可以再次調用 next()取得下一個值;value 包含可迭代對象的下一個值(done 為false),或者undefined(done 為 true)。done: true 狀態稱為“耗盡”。
// 可迭代對象 let arr = ['foo', 'bar']; // 迭代器工廠函數 console.log(arr[Symbol.iterator]); // f values() { [native code] } // 迭代器 let iter = arr[Symbol.iterator](); console.log(iter); // ArrayIterator {} // 執行迭代 console.log(iter.next()); // { done: false, value: 'foo' } console.log(iter.next()); // { done: false, value: 'bar' } console.log(iter.next()); // { done: true, value: undefined }
提前終止迭代器的方式:
for-of 循環通過 break 、 continue 、 return 或 throw 提前退出 解構操作并未消費所有值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 }; } }, return() { console.log('Exiting early'); return { done: true }; } }; } } let counter1 = new Counter(5); for (let i of counter1) { if (i > 2) { break; } console.log(i); } // 1 // 2 // Exiting early 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 let counter3 = new Counter(5); let [a, b] = counter3; // Exiting early
并非所有迭代器都是可關閉的。要知道某個迭代器是否可關閉,可以測試這個迭代器實例的 return 屬性是不是函數對象。不過,僅僅給一個不可關閉的迭代器增加這個方法并不能讓它變成可關閉的。這是因為調用 return()不會強制迭代器進入關閉狀態。即便此,return() 方法還是會被調用。let a = [1, 2, 3, 4, 5]; let iter = a[Symbol.iterator](); iter.return = function() { console.log('Exiting early'); return { done: true }; }; for (let i of iter) { console.log(i); if (i > 2) { break } } // 1 // 2 // 3 // 提前退出 for (let i of iter) { console.log(i); } // 4 // 5