迭代器与生成器

2018-11-18  本文已影响0人  王小滚

为什么我们需要迭代器?

var colours = ['red','green','yellow'];
for (var i = 0; i < colours.length; i ++) {
  console.log(colours[i]);
}

以上代码是我们最为熟悉的一种循环语句了吧,我们通过一个变量i来跟踪数组的索引,但是这种代码有没有问题呢,如果是多个循环嵌套,我们需要使用多个跟踪变量,一不小心就会出现使用错误变量,导致程序出错的问题,那么迭代器的出现就是为了消除这种复杂性并减少循环中的错误。

什么是迭代器?

迭代器是一种特殊的对象,它具有一些专门为迭代过程设计的专有接口。让我们用ES5的语法创建一个迭代器。

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 iterator = createIterator([1,2,3,4,5]);

从上面这段代码中,我们可以看出迭代器的特点:

  1. 迭代器对象有一个next()方法,每次调用next()方法都会返回出一个结果对象。
  2. 返回的结果对象有两个属性: done是一个布尔值表示是否完成迭代和value,表示下一个要返回的值

什么是生成器?

生成器是一种返回迭代器的函数。

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

// 生成器的调用方法与普通函数相同,只是它返回出一个迭代器
let iterator = createIterator();

console.log(iterator.next().value); // 1
console.log(iterator.next().value); // 2
console.log(iterator.next().value); // 3

我们通过yield来制定调用迭代器的next()方法的返回值以及返回顺序

for-of循环

回到我们最开始给出的循环内部索引跟踪的相关问题,解决这个问题有两个方案:一个是迭代器,还有一个就是for-of循环。
for-of每执行一次都会调用可迭代对象的next()方法,并将迭代器返回的结果对象的value属性存储在一个变量中,循环将持续执行这一过程直到返回对象的done属性的值为true。

可迭代对象

在ES6中,所有的集合对象和字符串都是可迭代对象,这些对象中都有默认的迭代器,我们可以通过Symbol.iterator来访问对象默认的迭代器。

let values = [1,2,3,4,5];
let iterator = values[Symbol.iterator]();
console.log(iterator.next()); // {value:1, done:false}

创建可迭代对象

默认情况下,开发者定义的对象都是不可迭代对象,但是如果给Symbol.iterator属性添加一个生成器,则可以将其变成一个可迭代对象。

let collection = {
  items: [1,2,3,4],
  *[Symbol.iterator] () {
    for (let item of this.items) {
      yield item;
    }
  }
}

在这个实例中,先创建了一个生成器并将其赋值给对象的Symbol.iterator属性来创建默认的迭代器,而在生成器中,通过for-of循环迭代items数组并用yield返回每一个值。对象默认的迭代器返回值有属性items自动生成。

高级迭代器功能

(1)给迭代器传递参数

在之前的介绍中,我们知道迭代器有一些向外传值的方法,既可以用迭代器的next()方法返回值,也可以通过生成器yield关键字生成值。如果我们给迭代器的next()方法传递参数,则这个参数的值就会代替生成器内部上一条yield语句的返回值。

function *createIterator() {
  let first = yield 1;
  let second = yield first + 2;
  yield second + 3;
}
let iterator = createIterator();
console.log(iterator.next()); // {value: 1, done: false}
console.log(iterator.next(3)); // {value: 5, done: false}
console.log(iterator.next(4)); // {value: 7, done: false}

当我们第一次调用next方法的时候无论传入什么参数都会被丢弃,因此传入的参数是用来替代上一次yield的返回值,而在第一次调用next方法之前,不会执行任何yield语句。

(2)生成器返回语句
function *createIterator() {
  yield 4;
  return 42;
}
let iterator = createIterator();
console.log(iterator.next()); // {value: 4, done: false}
console.log(iterator.next()); // {value: 42, done: true}
(3)委托生成器
function *createNumberIterator() {
   yield 1;
   yield 2;
}
function *createColorIterator() {
   yield red;
   yield blue;
}

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

这里的生成器createCombinedIterator先后委托了另外的两个生成器。

异步任务执行

function run (taskDef) {
    // 创建一个无使用限制的迭代器
    let task = taskDef();
    // 开始执行任务
    let result = task.next();
    // 循环调用next()函数
    function step () {
        if (!result.done()) {
            result = task.next();
            step();
        }
    }
    step(); // 开始执行迭代
}
上一篇 下一篇

猜你喜欢

热点阅读