学习ES6(三)—— Iterator和Generator

210 阅读4分钟

迭代器(Iterator)和生成器(Generator)

Iterator

什么是迭代器

迭代器是一种一次性使用的对象,可以用来遍历某个数据结构。该对象需符合迭代器协议。

迭代器协议

迭代器API使用next()方法,在可迭代对象中遍历数据。

next()方法返回的迭代器对象IteratorResult包含done和value两个属性:

  • done(boolean):是否还可以再次调用next()取得下一值;

    • done: false 表示可以取得下一值,即所对应的value值;
    • done: true 表示状态为“耗尽”,无法再取得下一值,value为undefined
  • value:包含可迭代对象的下一值(done: false),或为undefined(done: true)

// 创建一个迭代器对象来访问数组
const names = ["kobe", "zj", "cate"];
let index = 0;
const namesIterator {
    next: function() {
        if(index < names.length) {
            return { done: false, value: names[index++] }
        } else {
            return { done: true, value: undefined }
        }
    }
}
// 使用迭代器来访问数组元素,当迭代器到达done: true状态时,后续调用next()就一直返回同样值
console.log(namesIterator.next());

生成迭代器的函数

创建一个有限的迭代器

function createArrayIterator(arr) {
    let index = 0;
    return {
        next: function() {
            if(index < arr.length) {
                return { done: false, value: arr[index++] }
            } else {
                return { done: true, value: undefined }
            }
        }
    }
}

创建一个无限的迭代器:done永远不为true

function createNumberIterator() {
    let index = 0;
    return {
        next: function() {
            return { done: false, value: index++ }
        }
    }
}

什么是可迭代对象

一个对象实现了可迭代协议时,为可迭代对象。该对象必须实现@@iterator方法。

可迭代协议

必须暴露一个使用Symbol.iterator作为键的属性,来作为“默认迭代器”。该默认迭代器属性要引用一个迭代器工厂函数,调用该工厂函数时,返回一个新迭代器。

// 创建一个可迭代对象,相当于对创建迭代器代码的封装
const iterableObj = {
    names: ["kobe", "zj", "cate"],
    [Symbol.iterator]: function() { // 迭代器工厂函数
        let index = 0;
        return {
            // 这里返回一个迭代器
            next: () => { // 箭头函数不绑定this
                if(inedx < this.names.length) {
                    return { done: false, value: this.names[index++] }
                } else {
                    return { done: true, value: undefined }
                }
            }
        }
    }
}
console.log(iterableObj[Symbol.iterator]); // [Function: [Symbol.iterator]]
console.log(iterableObj[Symbol.iterator]().next()); // {done: false, value: "kobe"}

内置创建可迭代对象

平时创建的很多原生对象已经实现了可迭代协议,会生成一个迭代器对象,包括:

String、Array、Map、Set、arguments对象、NodeList集合等DOM集合类型

可迭代对象的应用场景

  1. for...of场景:相当于每次遍历可迭代对象中的迭代器,done: false时,将value取出赋值给item;done: true,自动停止
for(const item of iterableObj) {
    console.log(item);
}
  1. 展开语法
console.log([...iterableObj]); // ['kobe', 'zj', 'cate']
  1. 数组解构
const names = ["lsy", "zj"];
const [name1, name2] = names;
  1. 创建一些其他对象时
// Array.from()
const arr = Array.from(arguments);
// Set构造函数
const set = new Set(iterableObj);
// Map构造函数
let pairs = names.map((x, i) => [x, i]); // names见3中
console.log(pairs); // [['lsy', 0], ['zj', 1]]
let map = new Map(pairs);
console.log(map); // Map(2) {'lsy'=>0, 'zj'=>1}
  1. Promise.all():接收由期约组成的可迭代对象
Promise.all(iterableObj).then(res => {
    console.log(res); // ['kobe', 'zj', 'cate']
}

自定义类的可迭代性,以及提前终止迭代器

创建一个教室类,创建出来的对象都是可迭代对象

class Classroom {
    constructor(address, name, students) {
        this.address = address;
        this.name = name;
        this.students = students;
    }
    entry(newStudent) {
        this.students.push(newStudent);
    }
    [Symbol.iterator]() {
        let index = 0;
        return {
            next: () => {
                if(index < this.students.length) {
                    return { done: false, value: this.students[index++] }
                } else {
                    return { done: true, value: undefined }
                }
            }
        },
        // 监听迭代器是否提前终止了
        return: () => {
            console.log("迭代器提前终止了");
            return { done: true, value: undefined }
        }
    }
}
const classroom = new Classroom("3栋5楼205", "计算机教室", ["james", "kobe", "curry", "lsy"]);
classroom.entry("zj");

// 这样创建出来的对象为可迭代对象,可以使用for...of遍历
for(const stu of classroom) {
  console.log(stu);
  if(stu === "why") break; // 强制性将迭代器停掉
}

Generator

什么是生成器

生成器的形式是一个函数:(1)它需要在函数名称前加*;(2)生成器函数可以通过yield关键字控制函数的执行流程;(3)调用生成器函数并不会执行函数代码,但是会产生一个生成器对象

// 生成器函数声明
function* generatorFn() {}

// 生成器函数表达式
let generatorFn = function* () {}

// 作为对象字面量方法的生成器函数
let foo = {
    * generatorFn() {}
}

// 作为类实例方法的生成器函数
class Foo {
    * generatorFn()
}

// 作为类静态方法的生成器函数
class Bar {
    static * generatorFn() {}
}

生成器函数的执行

生成器函数遇到yield之前会正常执行,遇到yield后,停止执行,函数作用域的状态会被保留

当生成器对象继续调用next()方法后,又恢复执行,相当于继续执行下一段代码

迭代器的next是会返回值的,yield后面的内容会作为next()中的value值来返回,此时done: false;return后的内容也会作为next()中的value值返回,此时done: true

function* foo() {
    console,log("函数开始执行");
    
    const value1 = 100;
    console.log("第一段代码:", value1);
    yield value1
    
    const value2 = 200;
    console.log("第二段代码:", value2);
    yield value2
    
    console.log("函数执行结束");
    return 123
}

const generator = foo(); // 得到生成器对象
generator.next(); // 第一次调用next(),执行到第一个yield停止,则相当于执行第一段代码(第一个yield以前的代码){done: false, value: 100}
generator.next(); // 执行第二段代码 {done: false, value: 200}
generator.next(); // 执行第三段代码,函数执行结束 {done: true, value: 123}

在调用next函数时,可以给它传递参数,这个参数会作为上一个yield语句的返回值,相当于为本次的函数代码块执行提供了一个值

function* foo(initial) {
    console.log("函数开始执行");
    const value1 = yield initial + "aaa";
    const value2 = yield value1 + "bbb";
    const value3 = yield value2 + "ccc";
}

const generator = foo("zj");

const result1 = generator.next();
console.log("result1:", result1); // result1: { value: 'zjaaa', done: false }

const result2 = generator.next(result1.value);
console.log("result2:", result2); // result2: { value: 'zjaaabbb', done: false }

const result3 = generator.next(result2.value);
console.log("result3:", result3); // result3: { value: 'zjaaabbbccc', done: false }

生成器替代迭代器使用

function* createArrayIterator(arr) {
    // 写法一
    /*for(const item of arr) {
        yield item
    }*/
    // 写法二:为写法一的语法糖
    // yield*后面跟上一个可迭代对象,每次迭代可迭代对象中的一个值
    yield* arr
}

自定义类迭代-生成器实现

class Foo() {
    constructor() {
        this.values = [1, 2, 3];
    }
    *[Symbol.iterator]() {
        yield* this.values;
    }
}

提前终止生成器

return()

当生成器对象调用return()时,则提前终止迭代器,比如生成器对象generator,generator.return(15)则表明{value: 15, done: true}

throw()

该方法将传入的参数作为错误向生成器对象内部注入,若错误未被处理,则生成器关闭;处理,则不会关闭且恢复执行。错误处理会跳过对应的yield。

function* foo() {
    for(const x of [1, 2, 3]) {
        try {
            yield x;
        } catch(err) {}
    }
}
const generator = foo();
console.log(generator.next()); // {done: false, value: 1}
generator.throw('error message');
console.log(generator.next()); // {done: false, value: 3}
// 生成器在try/catch块中的yield处暂停执行,yield抛出了错误,生成器不再产生值2