Quiet
  • 主页
  • 归档
  • 分类
  • 标签
  • 链接
  • 关于我

bajiu

  • 主页
  • 归档
  • 分类
  • 标签
  • 链接
  • 关于我
Quiet主题
  • JavaScript

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

bajiu
前端

2021-04-23 13:30:00

循环语句的问题

var colors = ["red", "green", "blue"];
for(var i=0; i<colors.length; i++){
    console.log(colors[i]);
}

在ES6之前,这种标准的for循环,通过变量来跟踪数组的索引。如果多个循环嵌套就需要追踪多个变量,代码复杂度会大大增加,也容易产生错用循环变量的bug。

迭代器的出现旨在消除这种复杂性并减少循环中的错误。

什么是迭代器

我们先感受一下用ES5语法模拟创建一个迭代器:

function createIterator(items) {
    var i = 0;
    
    return { // 返回一个迭代器对象
        next: function() { // 迭代器对象一定有个next()方法
            var done = (i >= items.length);
            var value = !done ? items[i++] : undefined;
            
            return { // next()方法返回结果对象
                value: value,
                done: done
            };
        }
    };
}

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

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: undefiend, done: true}"
// 之后所有的调用都会返回相同内容
console.log(iterator.next());  // "{ value: undefiend, done: true}"

以上,我们通过调用createIterator()函数,返回一个对象,这个对象存在一个next()方法,当next()方法被调用时,返回格式{ value: 1, done: false}的结果对象。
因此,我们可以这么定义:迭代器是一个拥有next()方法的特殊对象,每次调用next()都返回一个结果对象。

借助这个迭代器对象,我们来改造刚开始那个标准的for循环【暂时先忘记ES6的for-of循环新特性】:

var colors = ["red", "green", "blue"];
var iterator = createIterator(colors);
while(!iterator.next().done){
    console.log(iterator.next().value);
}

并非如此,毕竟createIterator()只需写一次,就可以一直复用。不过ES6引入了生成器对象,可以让创建迭代器的过程变得更加简单。

什么是生成器

生成器是一种返回迭代器的函数,通过function关键字后的星号(*)来表示,函数中会用到新的关键字yield。

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

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

// 既然生成器返回的是迭代器,自然就可以调用迭代器的next()方法
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: undefiend, done: true}"
// 之后所有的调用都会返回相同内容
console.log(iterator.next());  // "{ value: undefiend, done: true}"

上面,我们用ES6的生成器,大大简化了迭代器的创建过程。我们给生成器函数createIterator()传入一个items数组,函数内部,for循环不断从数组中生成新的元素放入迭代器中,每遇到一个yield语句循环都会停止;每次调用迭代器的next()方法,循环便继续运行并停止在下一条yield语句处。

生成器的创建方式

ES6风格的对象方法简写方式:

let o = {
    *createIterator(items) { ... }
};

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

访问默认迭代器

可迭代对象,都有一个Symbol.iterator方法,for-of循环时,通过调用colors数组的Symbol.iterator方法来获取默认迭代器的,这一过程是在JavaScript引擎背后完成的。

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}"

内建迭代器

ES6中的集合对象,数组、Set集合和Map集合,都内建了三种迭代器:

  • entries() 返回一个迭代器,其值为多个键值对。如果是数组,第一个元素是索引位置;如果是Set集合,第一个元素与第二个元素一样,都是值。
  • values() 返回一个迭代器,其值为集合的值。
  • keys() 返回一个迭代器,其值为集合中的所有键名。
    如果是数组,返回的是索引;如果是Set集合,返回的是值(Set的值被同时用作键和值)。

迭代器高级功能

给迭代器传参

前面我们看到,在迭代器内部使用yield关键字可以生成值,在外面可以用迭代器的next()方法获得返回值。

其实next()方法还可以接收参数,这个参数的值就会代替生成器内部上一条yield语句的返回值。

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

let iterator = createIterator();

console.log(iterator.next());  // "{ value: 1, done: false }"
console.log(iterator.next(4)); // "{ value: 6, done: false }"
console.log(iterator.next(5)); // "{ value: 8, done: false }"
console.log(iterator.next());  // "{ value: undefined, done: true }"

参考

上一篇

函数柯里化

下一篇

FormData 对象详解

©2024 By bajiu.