1. 程式人生 > 程式設計 >ES6 - Iterator和Generator

ES6 - Iterator和Generator

Iterator:遍歷器

為某種資料結構提供的一種統一的遍歷機制

Iterator的遍歷過程如下:

  1. 建立一個指標物件,指向當前資料結構的起始位置。遍歷器物件本質上是一個指標物件
  2. 第一次呼叫指標物件的next方法,可以將指標指向資料結構的第一個成員
  3. 第n次呼叫指標物件的next方法,指標就指向資料結構的第二個成員
  4. 不斷呼叫next方法,直至指向資料結構的結束位置

每次呼叫next方法,返回一個包含資料結構當前成員資訊的物件,指標移動

資料結構當前成員資訊的物件有兩個屬性:valuedone,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函式有兩個特徵:

  1. function命令和函式名之間有一個*
  2. 函式體內部使用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);
}
複製程式碼