ES6 之 Iterator&Generator

Iterator由來

不推薦Iterator方法。 Iterator 函數是一個 SpiderMonkey 專有特性,而且會在某一時刻被刪除。
有一點,須要清楚的,就是「迭代協議」。迭代協議MDN說明node

// 簡單示例,摘自「深刻理解ES6」
function createIterator(items) {
    let i = 0;
    
    return {
        next: function() {
            let done = (i >= items.length);
            let value = !done ? items[i++] : undefined;
            
            return {
                done,
                value
            }
        }
    }
}
let iterator = createIterator([1, 2, 3]);

console.log(iterator.next()); // { done: false, value: 1 }
console.log(iterator.next()); // { done: false, value: 2 }
console.log(iterator.next()); // { done: false, value: 3 }
console.log(iterator.next()); // { done: true, value: undefined }
// 以後全部的調用都會返回相同的內容
console.log(iterator.next()); // { done: true, value: undefined }

Generator定義

生成器是一種返回迭代器的函數,經過function關鍵字後的星號(*)來表示,函數中會用到新的關鍵字yield。星號能夠緊挨着function關鍵字,也能夠在中間加一個空格。redux

function *createIterator() {
    yield 1;
    yield 2;
    yield 3;
}
let iterator = createIterator();
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 }

// 換個方法
function *createIterator(items) {
    for (let i = 0; i < items.length; i++) {
        yield items[i];
    }
}

let iterator = createIterator([1, 2, 3]);
// iterator 與前面代碼建立的 iterator 功能同樣,能夠試一下。
yield的使用限制
yield關鍵字只能在生成器內部使用,在其餘地方使用會致使程序拋出語法錯誤,即使在生成器的函數裏使用也如此。
function *createIterator(items) {
    items.forEach(function(item) {
        yield item + 1;
    });
}
// 會報語法錯誤 node ./iterator.js

從字面理解,yield關鍵字肯定在createIterator()函數內部,可是它與return關鍵字同樣,兩者都不能穿透函數邊界。嵌套函數中的return語句不能用做函數的返回語句,而此處嵌套函數中的yield語句會致使程序拋出語法錯誤。
生成器函數表達式&對象方法
經過上面的方法,關於函數表達式和對象方法,直接上代碼吧,更明白。數組

// 函數表達式
let createIterator = function *(items) {
    for (let i =0; i < items.length; i++) {
        yield items[i];
    }
}

let iterator = createIterator([1, 2, 3]);

// 對象方法
let o = {
    createIterator: function *(items) {
        yield items[i];
    }
}

let iterator = o.createIterator([1, 2, 3]);

可迭代對象 & for-of 循環

看過Symbol文章的小夥伴應該都知道,Symbol.iterator就是 well-known Symbol之一。可迭代對象就具備Symbol.iterator屬性,它是一種與迭代器密切相關的對象。它經過指定的函數能夠返回一個做用於附屬對象的迭代器。在ES6中,全部的集合對象(Array, Set, Map)和字符串都是可迭代對象,這些對象中都有默認的迭代器。固然,ES中也添加了for-of循環這些可迭代對象。瀏覽器

  • 迭代器
  • for-of循環

這是解決循環內部索引跟蹤問題的關鍵工具。
for-of循環每執行一次都會調用可迭代對象中的next()方法,並將迭代器返回的結果對象的value屬性存儲在一個變量中,循環將持續執行這一過程直到返回對象的done屬性爲true。異步

let values = [1, 2, 3];
for (let num of values) {
    console.log(num);
}
// 輸出:
// 1
// 2
// 3

示例說明:
for-of循環的代碼經過調用values數組的Symbol.iterator方法來獲取迭代器,這一過程是在Javascript引擎背後完成的。隨後迭代器的next()的方法被屢次調用,從其返回對象的value屬性讀取值並存儲在變量num中,直到對象的done爲true時循環退出,因此num不會賦值爲undefinedide

訪問默認迭代器

從上面的例子能夠看出,可迭代對象都有一個默認迭代器。這個迭代器可經過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 }

由此,咱們能夠判斷對象是否可迭代,是否是有更好的方法?工具

function isIterator(object) {
    return typeof object[Symbol.iterator] === "function";
}

console.log(isIterator([1, 2, 3]));  // true
console.log(isIterator("Hello"));  // true
console.log(isIterator(new Map()));  // true
console.log(isIterator(new Set()));  // true
console.log(isIterator(new WeakMap()));  // false
console.log(isIterator(new WeakSet()));  // false

建立可迭代對象

默認狀況下,開發者定義的對象都是不可迭代的對象,但若是給Symbol.iterator屬性添加一個生成器,則能夠將其變爲可迭代對象。ui

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 item of collection){
    console.log(item);
}
// 輸出:
// 1
// 2
// 3

內建迭代器

到這裏,應該明白,ES6中已經默認爲不少內建類型提供了內建迭代器,只有這些內容沒法實現目標時,才須要本身建立。ES6中有三種集合對象: 數組、Map與Set集合,他們內建瞭如下三種迭代器:this

  • entries() 返回值爲多個鍵值對迭代器
  • values() 返回值爲集合的迭代器
  • keys() 返回集合中全部鍵名迭代器

MDN 關於內建迭代器 說的比較簡單,我就按「深刻理解ES6」來詳細說一下吧。

entries() 迭代器

特色:每次調用next()方法時,entries()返回一個數組,數組中兩個元素,分別表示鍵和值。

- Array 第一個元素爲數字類型的索引,第二個元素爲值
- Set 第一個元素與第二個元素都是值,由於Set中值也做爲鍵來使用
- Map 第一個元素爲鍵,第二個元素爲值
示例:
let colors = [ "red", "green", "blue"];
let tracking = new Set([ 123, 456, 890]);
let data = new Map();
data.set("title", "ES6之Iterator&Generator");
data.set("formate", "net share");

for (let entry of colors.entries()) {
    console.log(entry);
}
// 輸出:
// [ 0, 'red' ]
// [ 1, 'green' ]
// [ 2, 'blue' ]

for (let entry of tracking.entries()) {
    console.log(entry);
}
// 輸出:
// [ 123, 123 ]
// [ 456, 456 ]
// [ 890, 890 ]

for (let entry of data.entries()) {
    console.log(entry);
}
// 輸出:
// [ 'title', 'ES6之Iterator&Generator' ]
// [ 'formate', 'net share' ]

values() 迭代器

特色: 返回集合中所存的全部值。
示例:

let colors = [ "red", "green", "blue"];
let tracking = new Set([ 123, 456, 890]);
let data = new Map();
data.set("title", "ES6之Iterator&Generator");
data.set("formate", "net share");

for (let entry of colors.values()) {
    console.log(entry);
}
// 輸出:
// red
// green
// blue

for (let entry of tracking.values()) {
    console.log(entry);
}
// 輸出:
// 123
// 456 
// 890 

for (let entry of data.values()) {
    console.log(entry);
}
// 輸出:
// ES6之Iterator&Generator
// net share

keys() 迭代器

特色:返回集合中存在的每個鍵。
示例:

let colors = [ "red", "green", "blue"];
    let tracking = new Set([ 123, 456, 890]);
    let data = new Map();
    data.set("title", "ES6之Iterator&Generator");
    data.set("formate", "net share");
    
    for (let entry of colors.keys()) {
        console.log(entry);
    }
    // 輸出:
    // 0
    // 1
    // 2
    
    for (let entry of tracking.keys()) {
        console.log(entry);
    }
    // 輸出:
    // 123
    // 456
    // 890
    
    for (let entry of data.keys()) {
        console.log(entry);
    }
    // 輸出:
    // title
    // formate

不一樣集合,會使用不一樣的默認迭代器,Array和Set使用的是values()迭代器,而Map則使用的是entries()迭代器。

string 迭代器

特色: 與Array相似。string也能夠經過下標訪問字符內容。因爲下標操做是編碼單元而非字符,因此沒法訪問雙字節符。在Unicode支持很差的版本(node或瀏覽器引擎),會出現錯誤。若是使用for-of,則不會出現這種問題,由於其操做的是字符而非編碼單元。
思考一個問題:展開運算符與非數組可迭代對象
若是對可迭代對象使用展開運算,會調用默認迭代器嗎?返回又是什麼呢?
非數組可迭代對象呢?
示例:

let o = {
    a: 'aaaa',
    b: 123,
    c: 'ddd',
    *[Symbol.iterator]() {
        yield this.a;
        yield this.b;
        yield this.c;
    }
}

let ar = [...o];
console.log(ar);
for(let item of ar) {
    console.log(item);
}
// 輸出
// [ 'aaaa', 123, 'ddd' ]
// aaaa
// 123
// ddd

迭代器高級功能

給迭代器傳參

此次先作示例,再作說明。
示例1:

function *createIterator() {
    let first = yield 1;
    let second = yield first + 2;
    yield second + 3;
}

let iterator = createIterator();
 console.log(iterator.next());
 console.log(iterator.next(4));
 console.log(iterator.next(6));
 console.log(iterator.next());
// 輸出:
// { value: 1, done: false }
// { value: 6, done: false }
// { value: 9, done: false }
// { value: undefined, done: true }

示例2:

function *createIterator() {
    let first = yield 1;
    let second = yield first + 2;
    yield first + 3;
}

let iterator = createIterator();
 console.log(iterator.next());
 console.log(iterator.next(4));
 console.log(iterator.next(6));
 console.log(iterator.next());
// 輸出:
// { value: 1, done: false }
// { value: 6, done: false }
// { value: 7, done: false }
// { value: undefined, done: true }

示例3:

function *createIterator() {
    let first = yield 1;
    let second = yield first + 2;
    yield second + 3;
}

let iterator = createIterator();
 console.log(iterator.next(5));
 console.log(iterator.next(4));
 console.log(iterator.next(6));
 console.log(iterator.next());
// 輸出:
// { value: 1, done: false }
// { value: 6, done: false }
// { value: 9, done: false }
// { value: undefined, done: true }

示例4

function *createIterator() {
    let first = 1;
    yield first;
    let second = yield first + 2;
    yield second + 3;
}

let iterator = createIterator();
 console.log(iterator.next(5));
 console.log(iterator.next(4));
 console.log(iterator.next(6));
 console.log(iterator.next());
// 示例:
// { value: 1, done: false }
// { value: 3, done: false }
// { value: 9, done: false }
// { value: undefined, done: true }

示例5:

function *createIterator() {
    let first = yield 1;
    let second = yield first + 2;
    yield second + 3;
}

let iterator = createIterator();
 console.log(iterator.next());
 console.log(iterator.next());
 console.log(iterator.next());
 console.log(iterator.next());
// 輸出:
// { value: 1, done: false }
// { value: NaN, done: false }
// { value: NaN, done: false }
// { value: undefined, done: true }

示例5的輸出結果,是否是很意外?yield返回變量與正常變量賦值有何不一樣?
分析:

  • 傳遞參數會替代上一次yield的返回值
  • 第一個next()執行,傳參無效。由於第一次調用yield時,以前沒有任何yield語句執行
  • 非yield返回值 ,不受next()參數影響
  • 在一個含參的yield語句中,表達式右側等價於第一次調用next()方法後下一個返回值。表達式左側等價於第二次調用next()方法後,在函數繼續執行前獲得的返回值。

拋出錯誤

這個也先示例,後說明。
示例1:

function *createIterator() {
    let first = yield 1;
    let second = yield first + 2;
    yield second + 3;
}

let iterator = createIterator();
 console.log(iterator.next());
 console.log(iterator.next(4));
 console.log(iterator.throw(new Error('Boom')));
 console.log(iterator.next(5));
// 輸出:
// { value: 1, done: false }
// { value: 6, done: false }
// xx/iterator.js:146
//    let second = yield first + 2;
//                 ^
//
// Error: Boom

分析:前兩個表達式正常求值,在繼續執行let second 求值前,錯誤就會被拋出,並阻止了代碼繼續執行。
這個過程,與直接拋出異常很類似,只是拋出的時機不一樣。
示例2

function *createIterator() {
    let first = yield 1;
    let second ;
    try {
        second = yield first + 2;
    } catch (ex){
        second = 6;
    }
    yield second + 3;
}

let iterator = createIterator();
 console.log(iterator.next());
 console.log(iterator.next(4));
 console.log(iterator.throw(new Error('Boom')));
 console.log(iterator.next(5));

// 輸出:
// { value: 1, done: false }
// { value: 6, done: false }
// { value: 9, done: false }
// { value: undefined, done: true }

分析:
用try...catch語句來捕獲異常,包裹着第二名語句。儘管這條語句自己沒有錯誤,但在給second賦值前,仍是會拋出錯誤,catch代碼塊捕捉到這個錯誤後,並把second = 6. 下一條yield語句繼續執行後,返回9.

返回語句

基本上兩個示例能夠歸納。
示例1

function *createIterator() {
    yield 1;
    return;
    yield 2;
    yield 3;
}

let iterator = createIterator();
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
// 輸出:
// { value: 1, done: false }
// { value: undefined, done: true }
// { value: undefined, done: true }

示例2

function *createIterator() {
    yield 1;
    return 33;
    yield 2;
    yield 3;
}

let iterator = createIterator();
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
// 輸出:
// { value: 1, done: false }
// { value: 33, done: true }
// { value: undefined, done: true }

委託生成

先看三個示例吧,這個只是語法規範。
示例1

function *createNumIterator() {
    yield 1;
    yield 2;
}

function *createColorIterator() {
    yield "red";
    yield "green";
}

function *createCombinedIterator() {
    yield *createNumIterator();
    yield *createColorIterator();
    yield true;
}

var iterator = createCombinedIterator();

console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());

// 輸出:
// { value: 1, done: false }
// { value: 2, done: false }
// { value: 'red', done: false }
// { value: 'green', done: false }
// { value: true, done: false }
// { value: undefined, done: true }

分析:語法規範,多個迭代器合併,能夠建立一個生成器,再給yield語句添加一個號,就能夠將生成數據的過程委託給其餘迭代器。當定義這些生成器時,只須要將號放置在關鍵字yield和生成器的函數名之間便可。
示例2

function *createNumIterator() {
    yield 1;
    yield 2;
    return 3;
}

function *createRepeatIterator(count) {
    for (let i = 0; i < count; i++){
        yield "repeat";
    }
}

function *createCombinedIterator() {
    let result = yield *createNumIterator();
    yield *createRepeatIterator(result);
}

var iterator = createCombinedIterator();

console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
// 輸出 :
// { value: 1, done: false }
// { value: 2, done: false }
// { value: 'repeat', done: false }
// { value: 'repeat', done: false }
// { value: 'repeat', done: false }
// { value: undefined, done: true }

分析:執行過程,先被委託給了createNumIterator(),返回值會被賦給變量result,執行到return 3時,返回精數值3。這個值隨後被傳入createRepeatIterator()方法。
可是,不管經過何種方式調用next()方法,數值3永遠不會被返回,它只存在於createCombinedIterator()的內部。若是要輸出3,參看示例3。
示例3

function *createNumIterator() {
    yield 1;
    yield 2;
    return 3;
}

function *createRepeatIterator(count) {
    for (let i = 0; i < count; i++){
        yield "repeat";
    }
}

function *createCombinedIterator() {
    let result = yield *createNumIterator();
    yield result;
    yield *createRepeatIterator(result);
}

var iterator = createCombinedIterator();

console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
// 輸出:
// { value: 1, done: false }
// { value: 2, done: false }
// { value: 3, done: false }
// { value: 'repeat', done: false }
// { value: 'repeat', done: false }
// { value: 'repeat', done: false }
// { value: undefined, done: true }

期待的3,出來了。

異步任務執行

生成器支持在函數中暫停代碼執行,能夠挖掘異步處理的更多方法。

簡單任務執行器

示例:

function run(taskDef) {

    // 建立迭代器
    let task = taskDef();
    // 開始執行任務
    let result = task.next();

    function step() {
        if (!result.done) {
            result = task.next();
            step();
        }
    }

    step();
}

run(function *() {
    console.log(1);
    yield;
    console.log(2);
    yield;
    console.log(3);
    yield;
});

分析:

  • 函數run()接受一個生成器做爲參數,這個函數定義了後續要執行的任務,生成一個迭代器並將它存儲在變量task中。
  • 首次調用next(),返回的結果被存儲起來,稍後繼續使用。
  • step()函數檢查result.done的值,false時,繼續執行next()方法,並執行step()操做。
  • 每次執行next()返回值會覆蓋變量result原來的值

傳參

示例:

function run(taskDef) {

    // 建立迭代器
    let task = taskDef();
    // 開始執行任務
    let result = task.next();

    function step() {
        if (!result.done) {
            result = task.next(result.value);
            step();
        }
    }

    step();
}

run(function *() {
   let value = yield 1;
   console.log(value);

   value = yield value + 3;
   console.log(value);
});

注意yield表達式求值。

異步

示例:

// redux-saga 經典應用

redux-saga
Iterator&Generator 之 MDN 說明

相關文章
相關標籤/搜索