ES6 - Iterator和Generator
Iterator:遍歷器
為某種資料結構提供的一種統一的遍歷機制
Iterator的遍歷過程如下:
- 建立一個指標物件,指向當前資料結構的起始位置。遍歷器物件本質上是一個指標物件
- 第一次呼叫指標物件的next方法,可以將指標指向資料結構的第一個成員
- 第n次呼叫指標物件的next方法,指標就指向資料結構的第二個成員
- 不斷呼叫next方法,直至指向資料結構的結束位置
每次呼叫next方法,返回一個包含資料結構當前成員資訊的物件,指標移動
資料結構當前成員資訊的物件有兩個屬性:value
和done
,value是當前成員的值,done是布林型值,用於標識遍歷是否結束
模擬next方法例子:
var it = makeIterator(['a' ,'b']);
it.next() // { value: 'a',done: false }
it.next() // { value: 'b',done: false }
it.next() // { value: undefined,done: true }
function makeIterator(array) {
var nextIndex = 0;
return {
next: function() {
return nextIndex < array.length ?
{ value: array[ nextIndex++ ],done : false } :
{ value: undefined,done: true };
}
}
}
複製程式碼
for ... of
迴圈會自動去找被遍歷資料結構的Iterator介面
for...of
內部呼叫的是資料結構的Symbol.iterator方法
部分資料結構原生具有Iterator介面,如Array
,Map
,Set
,String
,TypedArray
,函式的arguements物件
,NodeList物件
,而有的資料結構沒有原生的Iterator介面,如普通Object
但無論哪種資料結構,ES6規定資料結構預設的Iterator介面部署在資料結構的Symbol.iterator屬性,呼叫Symbol.iterator方法會得到當前資料結構預設的遍歷器生成函式
如果想要修改資料結構的預設Iterator介面:
const obj = { // obj是一個普通的Object
...
}
obj[Symbol.iterator] = function() {
return {
next: function() {
return {
value: 1,done: true
};
}
}
}
複製程式碼
使用Iterator介面的場景:
- 解構賦值
- 擴充套件運運算元 (任何部署了Iterator介面的資料結構都可以轉換為陣列)
- yield*
yield* 後面可以跟一個可遍歷結構
let generator = function* () {
yield 1;
yield* [2,3,4];
yield 5;
};
var iterator = generator();
iterator.next();
iterator.next();
...
複製程式碼
-
for...of
,Array.from()
,Map()
,Set()
,WeakMap()
,WeakSet()
,Promise.all()
,Promise.race()
字串的Iterator介面
var s = 'hi';
var iterator = s[Symbol.iterator](); // 遍歷器物件
iterator.next();
iterator.next();
iterator.next();
複製程式碼
Iterator介面與Generator函式
Generator函式是Symbol.iterator方法的最簡單實現
var myIterable = {};
myIterable[Symbol.iterator] = function* () {
yield 1;
yield 2;
yield 3;
};
[...myIterable] // [1,2,3]
// 或下面的簡潔寫法:
let obj = {
* [Symbol.iterator]() {
yield 'hello';
yield 'world';
}
};
for (let x of obj) {
console.log(x);
}
複製程式碼
遍歷器物件的return()和throw()
next()方法是必須的
next()和throw()方法是可選的
在for...of
中,如果迴圈提前退出(出錯或break、continue語句)就會呼叫return()
方法。常用於物件遍歷完成前需要清理釋放資源
throw()
方法主要配合Generator函式使用,一般遍歷器物件用不到
for...of
對於普通的物件
不能直接使用
普通Object使用for...in
會可以遍歷key
如何遍歷一個普通物件:
// 方法1
for (var key of Object.keys(obj)) {
console.log(key,obj[key])
}
// 方法2: 使用Generator函式將物件重新包裝
function* entries(obj) {
for (let key of Object.keys(obj)) {
yield [key,obj[key]];
}
}
for (let [key,value] of entries(obj)) {
console.log(key,value)
}
複製程式碼
Generator(生成器)
Generator函式會返回一個遍歷器物件(指標物件),由此可以理解Generator函式是一個遍歷器物件生成函式,返回的遍歷器物件可以依次遍歷Generator函式內部的每一個狀態
Generator函式有兩個特徵:
- function命令和函式名之間有一個*
- 函式體內部使用yield語句定義不同的內部狀態
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
};
var hw = helloWorldGenerator();
hw.next();
hw.next();
hw.next();
複製程式碼
*yield語句邏輯在此不再贅述
Generator函式可以不用yield語句,就變成了一個暫緩執行的函式
function* f() {
console.log('執行');
}
var gen = f(); // 如果f是一個普通函式,則在這一步就執行了
setTimeout(function () {
gen.next()
},2000)
複製程式碼
任意一個物件的Symbol.iterator方法等於該物件的遍歷器物件生成函式
var myIterable = {};
myIterable[Symbol.iterator] = function* () {
yield 1;
yield 2;
yield 3;
};
[...myIterable] // [1,3]
複製程式碼
next方法的引數
function* f () {
for(var i = 0; true; i++) { // 無限執行的Generator函式
var reset = yield i;
if (reset) { i = -1 };
}
}
var g = f();
g.next();
g.next();
...
g.next(true);
複製程式碼
yield語句的返回值是undefined,reset的值也是undefined,next方法帶有一個引數時,這個引數被當做已經執行完的上一條yield語句返回值(yield的返回值不是undefined了)
for...of可以自動遍歷Generator函式生成的Iterator物件
function* foo() {
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
return 6
};
for (let v of foo()) {
console.log(v);
};
複製程式碼
使用Generator函式和for...of迴圈實現斐波那契數列:
function* fibonacci () {
let [prev,curr] = [0,1];
for (;;) {
[prev,curr] = [curr,prev + curr];
yield curr;
}
}
for (let n of fibonacci()) {
if (n > 1000) break;
console.log(n);
}
複製程式碼