Life Will Change Lyn
  1. 1 Life Will Change Lyn
  2. 2 One Last You Jen Bird
  3. 3 BREAK IN TO BREAK OUT Lyn
  4. 4 かかってこいよ NakamuraEmi
  5. 5 Libertus Chen-U
  6. 6 Hypocrite Nush
  7. 7 Warcry mpi
  8. 8 Flower Of Life 发热巫女
  9. 9 Time Bomb Veela
  10. 10 Last Surprise Lyn
2017-03-19 18:24:42

迭代器和生成器

在JS中实现遍历的方法多种多样,比较常用的有标准的for循环,for in,数组的各种遍历方法等等。不同的方法有不同的优缺点,导致我们经常混合使用不同的遍历方法,不仅增加了自身出错的可能性,也使代码增加了额外的复杂度,于是ES6中新增了迭代器设计来专用于迭代对象。


迭代器的原理

  • 迭代器都是一个对象并且有next( )方法,该方法会返回一个结果对象。
  • 结果对象有两个属性。value: 对应的迭代值。done: 布尔值。未迭代完毕时为false,迭代完毕时为true。
  • 迭代完毕后若继续调用,value的值会是迭代器自身的return值,如果未提供则为undefined。

迭代器可以用生成器生成。但在使用ES6的生成器之前,我们先用原生方法创建迭代器来理清迭代器的实现逻辑。代码如下:

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

      return {
        done: done,
        value: value
      };
    }
  };
}
let 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: undefined, done: false}'

生成器的创建

正如我们看到的,要创建一个简单的迭代器也有些复杂,所以ES6封装了一个能返回迭代器的函数,就是生成器。下面是一个常见的生成器声明方式。

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

和正常的函数声明区别不大,只是在function关键字和函数名称中增加了一个星号.而yield关键字则指定了迭代器被next( )调用时返回的值,也就是结果对象的属性value。 始终要记住,生成器仍然是一个函数。所以也可以使用函数表达式声明,并且作为一个对象的方法。例子如下:

// 作为方法
let obj = {
  createIterator: function *() {
    yield 1;
    yield 2;
    yield 3;
  }
};
// 表达式声明
let createIterator = function *() {
  yield 1;
  yield 2;
  yield 3;
}

需要注意一点,yield关键字只能用在生成器的函数上下文。


可迭代对象

从上面的分析我们可以看出,每个生成器都是一个函数,如果要进行迭代,我们必须将需要迭代的每个值绑定到yield关键字来生成迭代器。虽然可以设置一个通用方法来绑定,但在迭代之前还要增加一步操作也显得有些麻烦。那么有没有自动生成迭代器的办法呢?

实际上ES6考虑到了这一点,对常用的有迭代需求的对象默认添加了一个知名符号——Symbol.iterator。该符号绑定了一个内置生成器,可以方便的直接生成一个默认迭代器。存在有这样一种特性的对象就是可迭代对象。下面来一一介绍拥有内置生成器的数据类型。

集合对象

ES6目前有三种集合对象类型:Array,Map和Set。这三者都拥有以下生成器:

  • entries( ): 返回包含键值对的迭代器。
  • values( ): 返回包含集合中的值的迭代器。
  • keys( ): 返回包含集合中的键的迭代器。

虽然这三种生成器都可用,但根据三种集合类型的具体用途,默认生成器有差异。values是数组与Set的默认生成器,entries是Map的默认生成器。

字符串

Symbol.iterator( )返回包含字符串单个字符的迭代器。

NodeList

默认生成器表现和数组一致,返回包含集合中的键的迭代器。

自定义对象

我们自己创建的对象没有默认生成器,但是可以通过Symbol.iterator属性来初始化一个生成器。例子如下,创建后对象的默认生成器表现和数组 [1,2,3]的默认生成器相同。

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

默认迭代器的应用场景

迭代器最常用的场景肯定就是用来遍历了。ES6新增了for-of循环来配合可迭代对象使用。

for-of

for-of循环会触发内置生成器方法生成默认迭代器,然后在每次执行时调用next( )方法获取结果对象的value属性值存储在声明的变量上,循环会持续到done属性变为true为止。例子如下:

let arr = [1, 2, 3];
for (let num of values) {
  console.log(num);   //分别输出1,2,3
}

拓展运算符

拓展运算符能作用于所有可迭代对象,在需要多个参数时,通过默认迭代器确定需要使用的值,插入顺序遵守迭代器返回值的顺序。例子如下:

let set = new Set([1, 2, 3]);
let arr = [...set];
console.log(arr); // [1, 2, 3]

生成器功能分析

掌握了以上迭代器的基本功能已经可以应对大多数迭代需求了,但迭代器的强大还不止于此,下面将从生成器内部入手,来分析迭代器的高级功能。

传递参数值替换yield语句值

我们知道通过调用生成器生成的迭代器的方法next( ),可以输出迭代结果对象的value值,也就是yield语句的值。当我们给next( )方法传入参数时,该参数就会成为上次结果对象的yield的值。例子如下:

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

需要注意由于取代的是上次结果对象的yield的值,所以第一次调用时传参无效。

抛出错误中断迭代器执行

因为yield语句的存在我们通过next( )方法可以退出函数。除此之外迭代器还提供了throw( )方法来通过抛出错误的方式退出函数。通过该方法退出函数后将无法往下继续执行,但如果生成器内部有 try-catch语句来捕捉throw( )的时机,则可以继续往下执行。例子如下:

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

  try {
    second = yield first + 1;  // 抛出错误位置
  } catch (ex) {
    second = 3;
  }
  yield second + 1;
}

let iterator = createIterator();

console.log(iterator.next()); // {value: 1, done: false}
console.log(iterator.next()); // {value: 2, done: false}
console.log(iterator.throw(new Error())); // {value: 4, done: false}

第三次调用throw( )方法抛出错误时,本应在赋值给 second时抛出错误跳出函数,被捕捉后就可以继续往下执行yield语句返回新的结果对象。

生成器内部的return语句

生成器函数执行完毕时最后一个结果对象的done值会变为true。我们也可以像普通函数一样通过return语句提前结束函数,return的值会成为最后一个结果对象的value值。伺候如果继续调用next( ),value将会还原为undefined。需要注意for-of和拓展运算符不会读取到最后的value值,因为它们会先判断done值,为true就停止操作。例子如下:

function *createIterator() {
  yield 1;
  return 2;
}
let iterator = createIterator( );
console.log(iterator.next( )); // {value: 1, done: false} 
console.log(iterator.next( )); // {value: 2, done: true} 
console.log(iterator.next( )); // {value: undefined, done: true}

生成器委托

不要忘了生成器本质上还是函数,所以我们可以在函数内部通过执行新的生成器函数合并生成迭代器。例子如下:

function *createIterator() {
  yield 1;
  yield *createOtherIterator();
  yield 4;
}
function *createOtherIterator() {
  yield 2;
  yield 3;
}
let iterator = createIterator();
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: 4, done: false}
console.log(iterator.next()); // {value: undefined, done: true}

剩下的和异步编程相关的应用将总结在以后的相关文章模块中。

-- EOF --

添加在分类「 前端开发 」下,并被添加 「JavaScript」 标签。

文章目录

 - [迭代器的原理](#迭代器的原理)
 - [生成器的创建](#生成器的创建)
 - [可迭代对象](#可迭代对象)
   - [集合对象](#集合对象)
   - [字符串](#字符串)
   - [NodeList](#nodelist)
   - [自定义对象](#自定义对象)
 - [默认迭代器的应用场景](#默认迭代器的应用场景)
   - [for-of](#for-of)
   - [拓展运算符](#拓展运算符)
 - [生成器功能分析](#生成器功能分析)
   - [传递参数值替换yield语句值](#传递参数值替换yield语句值)
   - [抛出错误中断迭代器执行](#抛出错误中断迭代器执行)
   - [生成器内部的return语句](#生成器内部的return语句)
   - [生成器委托](#生成器委托)
回到首页