什么是迭代器?
迭代器是被設計專用于迭代的對象,帶有特定接口。所有的迭代器對象都擁有 next() 方
法,會返回一個結果對象。該結果對象有兩個屬性:對應下一個值的 value ,以及一個布爾
類型的 done ,其值為 true 時表示沒有更多值可供使用。迭代器持有一個指向集合位置的
內部指針,每當調用了 next() 方法,迭代器就會返回相應的下一個值。
若你在最后一個值返回后再調用 next() ,所返回的 done 屬性值會是 true ,并且
value 屬性值會是迭代器自身的返回值( return value ,即使用 return 語句明確返回的
值)。該“返回值”不是原數據集的一部分,卻會成為相關數據的最后一個片段,或在迭代器未
提供返回值的時候使用 undefined 。迭代器自身的返回值類似于函數的返回值,是向調用者
返回信息的最后手段。
什么是生成器?
生成器( generator )是能返回一個迭代器的函數。生成器函數由放在 function 關鍵字之
后的一個星號( * )來表示,并能使用新的 yield 關鍵字。將星號緊跟在 function 關鍵
字之后,或是在中間留出空格,都是沒問題的。
// 生成器
function *createIterator(items) {
for (let i = 0; i < items.length; i++) {
yield items[i];
}
}
// 迭代器
let iterator = createIterator([1, 2, 3]);
// 迭代操作
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next()); // "{ value: 2, done: false }"
console.log(iterator.next()); // "{ value: 3, done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"
// 之后的所有調用
console.log(iterator.next()); // "{ value: undefined, done: true }"
生成器函數表達式
你可以使用函數表達式來創建一個生成器,只要在 function 關鍵字與圓括號之間使用一個
星號( * )即可。
例子:
// 生成器函數表達式
let createIterator = function *(items) {
for (let i = 0; i < items.length; i++) {
yield items[i];
}
};
let iterator = createIterator([1, 2, 3]);
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next()); // "{ value: 2, done: false }"
console.log(iterator.next()); // "{ value: 3, done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"
// 之后的所有調用
console.log(iterator.next()); // "{ value: undefined, done: true }"
生成器對象方法
//方式一
var o = {
createIterator: function *(items) {
for (let i = 0; i < items.length; i++) {
yield items[i];
}
}
};
// 方式二
var o = {
*createIterator(items) {
for (let i = 0; i < items.length; i++) {
yield items[i];
}
}
};
let iterator = o.createIterator([1, 2, 3]);
可迭代對象與 for-of 循環
與迭代器緊密相關的是,可迭代對象( iterable )是包含 Symbol.iterator 屬性的對象。這
個 Symbol.iterator 知名符號定義了為指定對象返回迭代器的函數。在 ES6 中,所有的集合
對象(數組、 Set 與 Map )以及字符串都是可迭代對象,因此它們都被指定了默認的迭代
器。可迭代對象被設計用于與 ES 新增的 for-of 循環配合使用。
生成器創建的所有迭代器都是可迭代對象,因為生成器默認就會為 Symbol.iterator 屬
性賦值。
訪問默認迭代器
你可以使用 Symbol.iterator 來訪問對象上的默認迭代器,就像這樣
let values = [1, 2, 3];
let iterator = values[Symbol.iterator]();
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next()); // "{ value: 2, done: false }"
console.log(iterator.next()); // "{ value: 3, done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"
創建可迭代對象
開發者自定義對象默認情況下不是可迭代對象,但你可以創建一個包含生成器的
Symbol.iterator 屬性,讓它們成為可迭代對象。例如:
let collection = {
items: [],
*[Symbol.iterator]() {
for (let item of this.items) {
yield item;
}
}
};
collection.items.push(1);
collection.items.push(2);
collection.items.push(3);
for (let x of collection) {
console.log(x);
}
集合的迭代器
ES6 具有三種集合對象類型:數組、 Map 與Set。這三種類型都擁有如下的迭代器,有助于
探索它們的內容:
- entries() :返回一個包含鍵值對的迭代器;
- values() :返回一個包含集合中的值的迭代器;
- keys() :返回一個包含集合中的鍵的迭代器。
傳遞參數給迭代器
當一個參數被傳遞給next() 方法時,該參數就會成為生成器內部 yield 語句的值。
例如:
function *createIterator() {
let first = yield 1;
let second = yield first + 2; // 4 + 2
yield second + 3; // 5 + 3
}
let iterator = createIterator();
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next(4)); // "{ value: 6, done: false }"
console.log(iterator.next(5)); // "{ value: 8, done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"
對于 next() 的首次調用是一個特殊情況,傳給它的任意參數都會被忽略。由于傳遞給
next() 的參數會成為 yield 語句的值,該 yield 語句指的是上次生成器中斷執行處的語
句;而 next() 方法第一次被調用時,生成器函數才剛剛開始執行,沒有所謂的“上一次中斷處的 yield 語句”可供賦值。因此在第一次調用next()時,不存在任何向其傳遞參數的理
由。
生成器的 Return 語句
由于生成器是函數,你可以在它內部使用 return 語句,既可以讓生成器早一點退出執行,
也可以指定在 next() 方法最后一次調用時的返回值。在本章大多數例子中,對迭代器上的
next() 的最后一次調用都返回了 undefined ,但你還可以像在其他函數中那樣,使用
return 來指定另一個返回值。在生成器內, return 表明所有的處理已完成,因此 done
屬性會被設為 true ,而如果提供了返回值,就會被用于 value 字段。此處有個例子,單
純使用 return 讓生成器更早返回:
function *createIterator() {
yield 1;
return;
yield 2;
yield 3;
}
let iterator = createIterator();
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"
生成器委托
在某些情況下,將兩個迭代器的值合并器一起會更有用。生成器可以用星號( * )配合
yield 這一特殊形式來委托其他的迭代器。正如生成器的定義,星號出現在何處是不重要
的,只要落在 yield 關鍵字與生成器函數名之間即可。此處有個范例:
function *createNumberIterator() {
yield 1;
yield 2;
}
function *createColorIterator() {
yield "red";
yield "green";
}
function *createCombinedIterator() {
yield *createNumberIterator();
yield *createColorIterator();
yield true;
}
var iterator = createCombinedIterator();
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next()); // "{ value: 2, done: false }"
console.log(iterator.next()); // "{ value: "red", done: false }"
console.log(iterator.next()); // "{ value: "green", done: false }"
console.log(iterator.next()); // "{ value: true, done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"
結語
迭代器是 ES6 重要的一部分,并且也是語言若干關鍵元素的根源。在表面上,迭代器提供了一種使用簡單接口來返回值序列的簡單方式。然而,在ES6中使用迭代器,還有著種種更加復雜的方式。
Symbol.iterator 符號被用于定義對象的默認迭代器。內置對象與開發者自定義對象都可以使用這個符號,以提供一個能返回迭代器的方法。當Symbol.iterator在一個對象上存在時,該對象就會被認為是可迭代對象。
for-of 循環在循環中使用可迭代對象來返回一系列數據。與使用傳統 for 循環進行迭代相
比,使用 for-of 要容易得多,因為你不再需要追蹤計數器并控制循環何時結束。 for-of
循環會自動從迭代器中讀取所有數據,直到沒有更多數據為止,然后退出循環。
為了讓 for-of 更易使用,ES6中的許多類型都具有默認的迭代器。所有的集合類型(也就是數組、 Map 與 Set )都具有迭代器,讓它們的內容更易被訪問。字符串同樣具有一個默認迭代器,能更加輕易地迭代字符串中的字符(而非碼元)。
擴展運算符能操作任意的可迭代對象,同時也能更簡單地將可迭代對象轉換為數組。轉換工
作會從一個迭代器中讀取數據,并將它們依次插入數組。
生成器是一個特殊的函數,可以在被調用時自動創建一個迭代器。生成器的定義用一個星號
( * )來表示,使用 yield 關鍵字能指明在每次成功的 next() 方法調用時應當返回什么
值。
生成器委托促進了對迭代器行為的良好封裝,讓你能將已有的生成器重用在新的生成器中。
通過調用 yield * 而非 yield ,你就能把已有生成器用在其他生成器內部。這種處理方式
能創建一個從多個迭代器中返回值的新迭代器。
生成器與迭代器最有趣、最令人激動的方面,或許就是可能創建外觀清晰的異步操作代碼。
你不必到處使用回調函數,而是可以建立貌似同步的代碼,但實際上卻使用 yield 來等待異步操作結束。