ES6 学习笔记(十一)迭代器和生成器函数

发布于 2021-06-06  392 次阅读


1、前言

JavaScript提供了许多的方法来获取数组或者对象中的某个元素或者属性(迭代)。从以前的for循环到之后的filter、map再到后来的for...in和for...of的迭代机制。只要具有iterator接口的都可被迭代。

2、迭代器 Iterator

2.1 含义

迭代器(iterator)为各种数据结构,提供一个统一的、简便的访问接口,简单的说,迭代可以是数组或对象的遍历方式。它使得数据结构的成员能够按某种次序排列,如上面所说,只要部署 Iterator 接口,就可以完成遍历操作。即依次处理该数据结构的所有成员。

2.2 工作过程

  • 首先,创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。
  • 第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员
  • 第二次调用指针对象的next方法,指针就指向数据结构的第二个成员,以此类推。
  • 不断调用指针对象的next方法,直到它指向数据结构的结束位置

2.3 Symbol.iterator属性

2.3.1 简介

ES6 规定,默认的 Iterator 接口部署在数据结构的Symbol.iterator属性。也就是说,只要某种数据结构具有iterator接口,就是可遍历的。常见的具有这一特点的数据结构有:Array、Map、Set、String、TypedArray(类型化的数组)、函数的 arguments 对象、NodeList 对象

2.3.2 示例1:数组的iterator示例

let arr = [1, 2, 3]
let iter = arr[Symbol.iterator]()
console.log(iter.next());
console.log(iter.next());
console.log(iter.next());
console.log(iter.next());

运行结果:
{ value: 1, done: false }
{ value: 2, done: false }
{ value: 3, done: false }
{ value: undefined, done: true }

其中,上面的iter返回的是一个迭代器对象,由于arr数组只有3个元素,所以经过3次的next()调用之后,第四次指针指到尾部了,已经结束了,所以获取的value值为undefined。迭代已完成,所以返回done。

2.3.3 对象的Iterator

一个对象如果要具备可被for...of循环调用的 Iterator 接口,就必须在Symbol.iterator的属性上部署遍历器生成方法(原型链上的对象具有该方法也可)。

2.3.4 示例2 实现iterator接口的自定义类示例

class RangeIterator {
    constructor(start, stop, step) {
        this.value = start;
        this.stop = stop;
        this.step = step;
    }
    [Symbol.iterator]() {
        return this;
    }
    next() {
        let value = this.value;
        if (value < this.stop) {
            this.value += this.step;
            return { done: false, value: value };
        }
        return { done: true, value: undefined };
    }
}
function range(start, stop, step = 1) {
    return new RangeIterator(start, stop, step);
}
for (let value of range(0, 9, 2)) {
    console.log(value);
}

输出结果:
0
2
4
6
8

2.3.5 示例3 为对象添加iterator示例

// 给对象添加iterator接口
let obj = {
    data: [1, 2, 3, 4, 5, 6],
    [Symbol.iterator]() {
        const self = this;
        let index = 0;
        return {
            next() {
                if (index < self.data.length) {
                    return {
                        value: self.data[index++],
                        done: false
                    }
                } else {
                    return {
                        value: undefined,
                        done: true
                    }
                }
            }
        }
    }
}
let sum = 0
for (let d of obj) {
    sum += d
}
console.log(sum);

输出结果:
21

3、生成器 Generator

3.1、含义

前面的文章 中也提到过生成器,举个最简单的例子:

let gen = function* () {
    yield 1;
    yield 2;
    yield 3;
}
for (let n of gen()) {
    console.log(n);
}

很明显,生成器的函数的function后面有个*,函数中存在yield 关键字,在函数中,通过gen()进行函数调用并生成控制器,在这里是通过循环执行函数的。

再举个例子

let obj = {
    name: "Tom",
    age: 20,
    *[Symbol.iterator]() {
        yield this.name;
        yield this.age;
    }
}
console.log(obj[Symbol.iterator]().next());
console.log([...obj]);
for (let k of obj) {
    console.log(k);
}

输出结果:
{ value: 'Tom', done: false }
[ 'Tom', 20 ]
Tom
20

可以看到,Generator 函数返回的遍历器对象,只有调用next方法才会遍历下一个内部状态,所以其实提供了一种可以暂停执行的函数。yield表达式就是暂停标志。

3.2 工作过程

遍历器对象的next方法的运行逻辑:

  • 遇到yield表达式,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性值。
  • 下一次调用next方法时,再继续往下执行,直到遇到下一个yield表达式。
  • 如果没有再遇到新的yield表达式,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值。
  • 如果该函数没有return语句,则返回的对象的value属性值为undefined。

3.3 Generator 函数的return方法

举个例子:

// 生成器函数
let gen = function* () {
    yield 1;
    yield 2;
    yield 3;
    return 'ok'
}
let g = gen()
console.log(g.next());
console.log(g.next());
console.log(g.next());
console.log(g.next());
console.log(g.next());

输出结果:
{ value: 1, done: false }
{ value: 2, done: false }
{ value: 3, done: false }
{ value: 'ok', done: true }
{ value: undefined, done: true }

由于在遍历到第三个next()时循环就已经结束了。再次遍历返回OK,done为true,再次遍历,由于指针已经到队列末尾,所以值为undefined。

如果for...of循环提前退出(通常是因为出错,或者有break语句),就会调用return方法。如果一个对象在完成遍历前,需要清理或释放资源,就可以部署return方法。

3.4 示例:定义类Fib,实现Fibonacci数列的获取

Fibonacci数列的后一个数等于前两个数之和

// 斐波拉契数列
class Fib {
    constructor(num) {
        this.num = num
    }
    *[Symbol.iterator]() {
        let [a, b] = [0, 1]
        while (true) {
            [a, b] = [b, a + b]
            if (a > this.num) return
            yield a
        }
    }
}

let fibs = new Fib(100)
for (let f of fibs) {
    console.log(f);
}

输出结果:
1
1
2
3
5
8
13
21
34
55
89


活的像诗一样