迭代器和生成器
在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」
标签。