第8章 Iterators & Generators

迭代器和产生器

迭代器和产生器是ES6很重要的两个新特性,迭代器是继承了某个特定接口的对象;产生器则是一类特殊的函数,调用产生器可以返回一个迭代器。下面我们来探讨一下这两个新特性:

  1. 什么是迭代器,什么是产生器;
  2. 可迭代(iterable)和 for...of;
  3. 内置迭代器类型:数组,Maps, Sets, 字符串, NodeList;
  4. 高级迭代器功能;
  5. 迭代与异步编程(Asynchonous programming);

一.什么是迭代器,什么是产生器

1.迭代器 iterators

迭代器是继承了特定用于迭代的接口的对象。所有的迭代器都有一个next()方法, 返回一个对象, 包含2个属性: value, done
ES5创建一个迭代器函数:返回一个对象,对象包含next方法,调用next方法返回一个对象,包含value, done属性

function createIterator(items) {
    var i = 0;
    return {
        next: function() {
            var done = (i >= items.length);
            var value = !done ? items[i++] : undefined; 
            return {
                done: done,
                value: value
            };
        }
    };
}

var it = createIterator([1, 2, 3]);
it.next(); // {value: 1, done: false}
it.next(); // {value: 2, done: false}
it.next(); // {value: 3, done: false}
it.next(); // {value: undefined, done: true}

2.产生器 generators

1.产生器是一个返回迭代器的函数(Generator is a function that return an iterator),产生器可以用yield来控制函数的流程,停止或继续函数,使用function * 函数名()来表示,不能使用箭头函数表示产生器。

function *createGenerator() {
    yield 1;
    yield 2;
    yield 3;
}
// 调用产生器返回一个迭代器
let it = createGenerator();
it.next(); // {value: 1, done: false}
it.next(); // {value: 2, done: false}
it.next(); // {value: 3, done: false}
it.next(); // {value: undefined, done: true}

产生器最有意思的地方就是,每次yield语句执行之后就停止执行,要继续执行,需要调用迭代器的next()方法; 产生器的本质是函数(function),javascript中其余函数能用到的地方,产生器也能使用

2.Generator function expression

let createGenerator = function*(items) {
    for (let i = 0; i < items.length; i++) {
        yield items[i];
    }
};

3.yield关键词只能用于产生器内部,用在产生器内部的函数会抛出语法错误:

function *createGenerator(items) {
    
    items.forEach(function(item) {
        
        // 语法错误
        yield item + 1;
    });
    
}
// yield 不能跨函数边界

二.可迭代和for...of

什么类型的数据是可迭代的? 在介绍Symbol对象时, 我们知道一个对象要可迭代,则需要包含[Symbol.iterator]()属性。所有的集合对象(Array, Maps, Sets)和字符串都包含一个默认的迭代器,可以使用ES6新方法for...of.

1.创建可迭代对象

方法1:使用[Symbol.iterator]方法

class Log {
    constructor() {
        this.messages = [];
    }
    add(message) {
        this.messages.push({message, timestamp: Date.now()});
    }
    // 实现[Symbol.iterator]属性
    [Symbol.iterator]() {
        let i = 0;
        const messages = this.messages;
        return {
            next() {
                if (i > messages.length) {
                    return {value: undefined, done: true};
                }
                return {value: messages[i++], done: false};
            }
        };
    }
}
var log = new Log();
log.add("hello");
log.add("world");
// 对log实例进行迭代
for (let entry of log) {
    console.log(entry);
}
// 输出结果为:
Object {message: "hello", timestamp: 1474192362106}
Object {message: "world", timestamp: 1474192369195}

方法2:使用 *[Symbol.iterator]迭代器

let collection = {
    items: [],
    *[Symbol.iterator]() {
        for (let item of items) {
            yield item;
        }
    }
};
collection.items.push("a");
collection.items.push("b");
for (let x of collection) {
    console.log(x);
}
// "a" 
// "b"

2.访问默认迭代器

var arr = [1, 2, 4];
var it = arr[Symbol.iterator]();
it.next(); // {value: 1, done: false}
// ...

3.可迭代检测

可以通过一个对象是否包含[Symbol.iterator]函数来判断一个对象是否可迭代

function isIterable(obj) {
    return typeof obj[Symbol.iterator] === "function";
}

isIterable(new Set()); // true
isIterable("hello"); // true
isIterable(new WeakMap()); // false

三.内置迭代器

1.ES6有3个集合类型: Arrays, Sets, Maps, ES6也内置了3个迭代器,方便对内容进行导航:

  1. keys(): 返回集合中的keys;
  2. values(): 返回集合中的values, 这个也是Sets, Arrays的在for...of中的默认类型;
  3. entries(): 返回集合中的键值对, 这个是Maps在for...of中的默认类型; 对于Sets键keys和值values一样; 对于Arrays, 键为数字索引
let colors = ["red", "yellow", "blue"];
let set = new Set([1, 2, 3]);
let map = new Map([["name", "james"], ["age", 26]]);

for (let key of colors.keys()) {
    console.log(key);  // 0 1 2
}
// 数组默认情况
for (let value of colors.values()) {
    console.log(value); // "red" "yellow" "blue"
}
// 等价于
for (let value of colors) {
    console.log(value); // "red" "yellow" "blue"
}

for (let entry of colors.entries()) {
    console.log(entry); // [0, "red"] [1, "yellow"] [2, "blue"]
}


for (let key of set.keys()) {
    console.log(key);  // 1 2 3
}
// Sets默认情况
for (let value of set.values()) {
    console.log(value); // 1 2 3
}
for (let entry of set.entries()) {
    console.log(entry); // [1, 1] [2, 2] [3, 3]
}

for (let key of colors.keys()) {
    console.log(key);  // "name" "age"
}
for (let value of colors.values()) {
    console.log(value); // "james" 26
}
// Maps默认情况
for (let entry of colors.entries()) {
    console.log(entry); // ["name", "james"] ["age", 26]
}

2.entries()作为Maps默认的迭代器, 可以使用在for...of中使用解构

let data = new Map();
data.set("name", "james");
data.set("age", 26);

// 使用解构
for (let [key, value] of data) {
    console.log(key + "=" + value);
}
// name=james
// age=26

3.字符串使用迭代器
我们知道字符串本质上是字符的集合,字符串相当字符组成的数组

var s = "CHARS";
for (let c of s) {
    console.log(c);
}
// C H A R S

4.spread操作符
spread操作符可以作用于所有可迭代集合,使用集合默认的迭代器,比如Maps默认的迭代器为entries

let map = new Map([["name", "james"], ["age", 26]]);
var arr = [...map];
console.log(arr); // [["name", "james"], ["age", 26]]

四.高级迭代功能

1传递参数给迭代器

我们知道产生器调用返回迭代器, 迭代器遇见yield语句函数停止执行,等待下一次调用next(),我们可以传入参数到next(),这种功能是异步编程的基础(异步编程中会给示例)

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

var it = createIterator(); // 产生迭代器
it.next(); // 执行"yield 1" 并且函数停止 {value: 1, done: false}
it.next(5); // 执行let first = 5赋值 并且执行到 "yield first + 3" 然后停止 
            // {value: 8, done: false}
it.next(10); // 执行let second = 10赋值 并执行"yield second + 1" 然后停止
            //  {value: 11, done: false}
it.next(); // {value: undefined, done: true}

2.在迭代器中抛出错误 iterator.throw()

迭代器能够实现一个throw()方法,显示迭代器抛出错误,这对于异步编程很有用

function *createIterator() {
    let first = yield 1;
    let second = yield first + 3; // yield 4 + 3, then throw
    yield second + 1;              // 不再执行
}
let iterator = createIterator();
iterator.next(); // {value: 1, done: false}
iterator.next(4); // {value: 7, done: false}
iterator.throw(new Error("boom")); // 抛出错误,下面next()不再执行

可以使用 "try...catch"对错误进行处理

function *createIterator() {
    let first = yield 1;
    let second;
    try {
        second = yield first + 3; 
    } catch(ex) {
        second = 2; // 出错之后处理
    }
    yield second + 1;
}
let iterator = createIterator();
iterator.next(); // {value: 1, done: false}
iterator.next(4); // {value: 7, done: false}
iterator.throw(new Error("boom")); // {value: 3, done: false}
iterator.next(); // {value: undefined, done: true}

3.产生器中的return语句

在产生器中,return表示所有处理完成done: true

function *createIterator() {
    yield 1;
    return;
    yield 2;
}
var it = createIterator();
it.next(); // {value: 1, done: false}
it.next(); // {value: undefined, done: true}
// 后面的yield语句不再能够执行

可以给return指定一个返回值

function *createIterator() {
    yield 1;
    return 42;
    yield 12;
}

var it = createIterator();
it.next(); // {value: 1, done: false}
it.next(); // {value: 42, done: true}
it.next(); // {value: undefined, done: true}
it.next(); // {value: undefined, done: true}

4.委托产生器(delegate generators)

1.将2个迭代器结合成1个

function *createNumIterator() {
    yield 1;
    yield 2;
}
function *createColorIterator() {
    yield "red";
    yield "blue";
}
// 合为一个
function *combinedIterator() {
    yield *createNumIterator();
    yield *createColorIterator();
    yield true;
}

var it = combinedIterator();
it.next(); // {value: 1, done: false}
it.next(); // {value: 2, done: false}
it.next(); // {value: "red", done: false}
it.next(); // {value: "blue", done: false}
it.next(); // {value: true, done: false}
it.next(); // {value: undefined, done: true}

2.进一步使用产生器返回的值,可以完成复杂的任务

function *createNumIterator() {
    yield 1;
    yield 2;
    return 3; // 利用这个返回值
}
function *createRepeatIterator(count) {
    for (let i = 0; i < count; i++) {
        yield "repeat";
    }
}    
//合成1个
function *combinedIterator() {
    let result = yield *createNumIterator(); // 将返回的值赋给变量result
    yield *createRepeatIterator(result); // 将上面赋值的变量传入另一个产生器
}

var it = createRepeatIterator();

// 注意返回的3作为变量传入下个迭代器, 没有显示result
it.next(); // {value: 1, done: false}
it.next(); // {value: 2, done: false}
it.next(); // {value: "repeat", done: false}
it.next(); // {value: "repeat", done: false}
it.next(); // {value: "repeat", done: false}
it.next(); // {value: undefined, done: true}

// 若想显示result
function *combinedIterator() {
    let result = yield *createNumIterator(); // 将返回的值赋给变量result
    yield result;                            // 将result也产出
    yield *createRepeatIterator(result); // 将上面赋值的变量传入另一个产生器
}
it.next(); // {value: 1, done: false}
it.next(); // {value: 2, done: false}
it.next(); // {value: 3, done: false}
it.next(); // {value: "repeat", done: false}
it.next(); // {value: "repeat", done: false}
it.next(); // {value: "repeat", done: false}
it.next(); // {value: undefined, done: true}

五.异步编程

迭代器和产生器可以用于异步编程,控制函数流程,因为generator允许我们在代码执行过程中暂停代码,一般使用异步编程的有异步回调函数。

1.因为yield停止执行,等待下一次next()调用,将产生器当作参数

function run(taskDef) {
    
    // 创建迭代器, taskDef为产生器
    let task = taskDef();
    
    // 开始task, result为返回的对象
    let result = task.next();

    // 递归函数一直调用 next()
    function step() {
            
        // if there's no more to do
        if (!result.done) {
            result = task.next();
            step(); // 递归调用
        }

    }

    // 开始处理
    step();
    
}

实现run(),传入一个generator,包含多个yield

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

2.包含数据的异步调用

其原理就是利用next()传入参数,generator中yield可以有表达式赋值操作

function run(taskDef) {
    // 创建iterator
    var task = taskDef();
    
    // 开始任务
    var result = task.next();
    
    // 递归调用next()
    function step() {
        if (!result.done) {
            result = task.next(result.value); // 传入参数
            step();
        }
    }
    
    // 开始处理
    step();
}

调用run()

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

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

异步取回数据示例:
假设有3个json文件在 "data"目录下, 分别是tweets.json, friends.json, videos.json,包含一些数据,现在异步分别取回这些数据。

function genWrap(myGen) {
    // 创建迭代器
    let it = myGen();
    
    // 开始任务
    let result = it.next();
    
    // 递归调用next()
    function handle() {
        if (!result.done) {
            result = it.next(result.value); // 传入参数,将产出的值赋给变量
                // 比如此例将$.get("data/tweets.json")赋给tweets变量
            handle(); // 递归调用
        }
    }
    
    // 开始处理
    handle(); 
}

// 开始任务,调用genWrap()
genWrap(function *() {
    let tweets = yield $.get("data/tweets.json"); // 使用jquery的$.get() ajax方法
    console.log(tweets);
    let friends = yield $.get("data/friends.json");
    console.log(friends);
    let videos = yield $.get("data/videos.json");
    console.log(videos);
});

总结

迭代器和产生器极大的对可迭代数据结构操作提供了极大的便利,同时对异步编程来说十分的重要,因为generator和iterator对函数提供了流程控制。

你可能感兴趣的:(第8章 Iterators & Generators)