JavaScript迭代與迭代器(Iterable and Iterator)
我們在很前面的時候就講到了迭代器這么一個東西。那么他究竟是什么呢?又有什么樣的作用呢?本節我們就來講述 Iterables 與 Iterators。也就是可迭代性與迭代器。
概述
ES6 中新增了一個迭代的接口,叫做可迭代性(Iterable )。本節呢,將要向大家講述它是怎么工作的,以及它運用于那些 ECMAScript 語言類型中。
我們來看看迭代(Iteration )是什么,它分為兩個部分:
- Iterable: 可迭代性是一種數據結構,它希望使其元素可以訪問公共部分。它通過內置系統的一個方法,鍵為 Symbol.iterator。這個方法就是迭代器的工廠。
- Iterator: 用于遍歷數據結構的元素的指針。
以下的值具有可迭代性:
- Array
- String
- Map
- Set
- DOM 數據結構(在程序中工作的那部分)
那么具有可迭代性的對象可以做哪些事呢?如下所示:
(1)解構
let [a,b] = new Set(['a', 'b']);
console.log(a); // "a"
console.log(b); // "b"
(2)for-of 循環
for (let x of ['a', 'b', 'c']) {
console.log(x);
}
// "a"
// "b"
// "c"
(3)Array.from( ) 方法
let arr = Array.from(new Set(['a', 'b', 'c']));
// ["a", "b", "c"]
(4)展開操作符(...)
let arr = [...new Set(['a', 'b', 'c'])];
// ["a", "b", "c"]
(5)Map 和 Set 構造函數
let map = new Map([
[1,'a'],
[2,'b']
]);
let set = new Set(['a', 'b', 'c']);
(6)Promise.all( ) 、Promise.race( ) 方法
Promise.all(iterableOverPromises).then(···);
Promise.race(iterableOverPromises).then(···);
(7)yeild(*)
yield* anIterable;
看完以上的這些,記住了,基本上這節內容就大致清楚了。
可迭代性
這里我們將要講到可迭代性的思想,也就是說,可迭代到底是什么?
可迭代性的思想:
- 數據消費者( Data consumers ):JavaScript 具有“消費”數據的語言結構。例如,for-of 循環用于遍歷值,而擴展運算符(...)將值插入到數組或函數調用中。
- 數據源( Data Sources ):數據消費者可以從各種來源獲取值。例如,您可能需要遍歷數組的元素,或者Map 中的鍵值(使用 entries 方法)以及 String 實例中的字符等。
我們這里必須清楚,想要每個數據消費者都能獲取數據源,這是不切實際的。尤其是當我們創建新的源時(例如通過一個庫)。因此,ES6 就提供了 Iterable 的接口。數據消費者使用它,而數據源負責實現它。我們來看下圖:

迭代關系
這里有兩個知識:
- 源( Source ):如果一個鍵擁有 Symbol.Iterator 方法并返回迭代器時,它的值就被認為是可迭代的。迭代器是一個對象,它可以使用 next( ) 方法返回值。我們可以這樣說:這個方法每次使用時,都可以枚舉值。
- 消耗( Consumption ):數據使用者使用迭代器來檢索它們消耗的值。
這里我們來舉個之前的例子吧:
let set = new Set(['a','b','c']);
let keys = set.keys();
console.log(keys.next()); // {value: "a", done: false}
console.log(keys.next()); // {value: "b", done: false}
console.log(keys.next()); // {value: "c", done: false}
console.log(keys.next()); // {value: undefined, done: true}
上述示例中,我們使用了 keys() 方法與 next() 方法來進行該 Set 實例每個值的枚舉與輸出。就是因為 Set 擁有 Symbol.Iterator 方法。因此我們還可以使用下述的方式進行遍歷:
let set = new Set(['a','b','c']);
let iter = set[Symbol.iterator]();
console.log(iter.next()); // {value: "a", done: false}
console.log(iter.next()); // {value: "b", done: false}
console.log(iter.next()); // {value: "c", done: false}
console.log(iter.next()); // {value: undefined, done: true}
輸出結果一致,沒有問題。
可以看到,next( ) 返回包裝在對象中的每個項目,作為屬性值的值。布爾屬性 done 指示何時已達到項目序列的結束。
迭代和迭代器是所謂的協議(方法加上使用它們的規則)的一部分迭代。此協議的關鍵特性是它是順序的:迭代器一次返回一個值。 這意味著如果一個可迭代的數據結構是非線性的(如一顆樹),迭代將會使其線性化。
迭代的數據源
在概述中我舉了幾個例子。以下我使用 for-of 循環來迭代每個類型。我們一起來看下輸出的值是什么:
(1)Array
for (let x of ['a', 'b']) {
console.log(x);
}
// "a"
// "b"
(2)String
for (let x of 'Hello') {
console.log(x);
}
// "H"
// "e"
// "l"
// "l"
// "e"
(3)Map
let map = new Map()
.set('a', 1)
.set('b', 2);
for (let pair of map) {
console.log(pair);
}
// ["a", 1]
// ["b", 2]
(4)Set
let set = new Set()
.add('a')
.add('b');
for (let x of set) {
console.log(x);
}
// "a"
// "b"
(5)arguments
function printArgs() {
for (let x of arguments) {
console.log(x);
}
}
printArgs('a', 'b');
// "a"
// "b"
(6)DOM 數據結構
for (let node of document.querySelectorAll('div')) {
// ···
}
現在更加清晰了吧?舉出這么多例子的目的就是希望大家熟悉它。
迭代計算的數據
不是所有可迭代內容都必須來自數據結構,它也可以即時計算。
例如我們前面學習的 Array、Map、Set 都擁有 entries( )、keys( )、values( ) 三個方法。它們都是即時計算實例中的內容,再進行輸出。
- entries( ) 方法返回實例的 [key, value] 的數組。
- keys( ) 方法返回實例的鍵。
- values( ) 方法返回實例的值。
但是,我們需要知道,Object 類型是沒有迭代性可言的。因此它沒有 for-of 循環,只有 for-in 的遍歷。當然,在未來可能會有內置方法。
// 錯誤
for (let x of {a: 1, b: 2}) { // TypeError
console.log(x);
}
總結
本節把迭代的知識點講完了,但是具體的用法還是比較多的。需要我們不斷地實踐。當你忘記的時候,就回來看看,將這些可迭代的對象牢記在腦海中,在使用時,就不會出錯。
來自:http://www.jianshu.com/p/2d0187f30a54