1. 程式人生 > >關於生成器和迭代器的一些說明

關於生成器和迭代器的一些說明

在介紹生成器之前,迭代器的一些知識是與生成器相互關聯的。生成器官方的定義是:一種返回迭代器的函式,通過function關鍵字後的星號(*)來表示,函式中通常會用到新的關鍵在yield。NOTE:不能用箭頭函式來建立生成器。舉個官方的例子:

function *createIterator(){
    yield 1;
}

因為本質上來講,生成器終究是一個函式,所以其呼叫的方式和普通函式呼叫的方式相同。有區別的在於,在呼叫生成器函式後,需要繼續手動的進行其next方法。而這next的方法是來自於迭代器中,因為同定義闡述,在呼叫生成器函式後,此時就轉換成了迭代器。簡單的介紹下next方法,每次呼叫next都返回一個結果物件,這物件中包含兩個屬性,一個是value(表示返回的值),一個是done(是一個布林值),例子:{ value: 1, done:false}。如果在最後一個值後返回後再呼叫next方法, 則返回 { value:undefined,done:true}。拿上面的生成器舉例子:

var iterator = createIterator();

console.log(iterator.next())    // {value: 1, done: false}
console.log(iterator.next())    // { value: undefined, done: true}

next方法先講一半到這,後面還有結合yield會發現有趣的事。提到yield這個關鍵字,首先還是介紹一下它,沒有特別官方的解釋,大概的意思是,沒當執行完一條yield語句時,函式就會自動暫停執行,而且yield可以返回任何值或者是表示式。當然最重要的一點就是使用環境,它只可以在生成器內部使用

上面提到了在執行yield時,函式會暫停執行,那麼什麼時候才會繼續呢?這不,next方法又重新登場了,在使用next方法時,可以繼續當前函式,當然了再遇到下一個yield關鍵字時又會被暫停執行。這也就是為什麼,在生成器中,next方法永遠會比yield關鍵字多使用一次的原因(第一次用於開始迭代器)。

有趣的還沒結束,當yield停止函式時,跟在yield後面的表達時也好,其他值也好,都會被呼叫者接收到,這也是上面第一次呼叫next的時候,value等於1而不等於其他值。這時候next優秀的地方又來了,你以為只有簡單的繼續函式嗎,它可不服,next方法還可以接受一個引數用於代替yield後面的值被表示式接收。

function *createIterator(){
    var newNum = yield 1;
    console.log(newNum);    //4
}

var iterator = createIterator();
iterator.next().value    // 1
iterator.next(4)

另外需要注意的是,可迭代物件具有Symbol.iterator屬性,這是一種與迭代器密切相關的物件(從命名也能看出端倪)。在ES6中,所有的集合物件(陣列,Set集合以及Map集合)和字串都是可迭代的物件,這些物件中都有預設的迭代器。而for...of迴圈則需要用到可迭代物件的這些功能,這也是為什麼普通的物件型別無法使用這個for...of迴圈的原因。for...of迴圈每執行一次都會呼叫可迭代物件的next方法,是因為可以找到可迭代物件的Symbol.iterator屬性獲取迭代器(這一個過程是在JS引擎背後完成)。當結果物件的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 k of collection){
    console.log(k);
}

// 1
// 2
//3

====================

文章中很多概念問題是來自於深入理解ES6。