第八章 迭代器与生成器
《深入理解ES6》—— Nicholas C. Zakas
许多编程语言都将迭代器数据的方式从使用 for
循环转变到使用迭代器对象,
for
循环需要初始化变量以便追踪集合内的位置,
而迭代器则以编程方式返回集合中的下一个项。
1. 循环的问题
var colors = [ "red", "green", "blue" ];
for ( var i = 0, len = colors.length; i < len; i++ ) {
console.info( colors[ i ] );
}
此处使用 for
循环的标准方式,借助 i
变量来追踪 colors
数组中的位置索引。
虽然循环很直观,但当它被嵌套使用并要追踪多个位置索引时,情况就会变得非常复杂,额外的复杂度会引发错误。迭代器正是用来解决此问题的。
2. 迭代器
迭代器是被设计专用于迭代的对象,带有特定接口。
- 迭代器对象都有
next()
方法 - 迭代器持有一个指向集合位置的内部指针,每当调用
next()
会返回下一个值 next()
返回一个结果对象,结果对象有两个属性:value
和done
value
对应下一个值done
是布尔值,为true
时表示没有更多值可供使用
ES5 中创建迭代器:
function createIterator( items ) {
var i = 0;
return {
next: function() {
var done = ( i >= items.length );
var value = !done ? items[ i++ ]: undefined;
return {
done,
value
}
}
}
}
var iterator = createIterator( [ 1, 2, 3 ] );
iterator.next(); //=> {done: false, value: 1}
iterator.next(); //=> {done: false, value: 2}
iterator.next(); //=> {done: false, value: 3}
iterator.next(); //=> {done: true, value: undefined}
根据ES6制定的规则来书写迭代器,是有一点复杂。
幸好,ES6提供了生成器
3. 生成器
3.1. 创建
生成器(generator)是能返回一个迭代器的函数,
用 *function
表示,可使用 yield
关键字。
function *createIterator() {
yield 1;
yield 2;
yield 3;
}
let iterator = createIterator();
iterator.next(); //=> {value: 1, done: false}
iterator.next(); //=> {value: 2, done: false}
iterator.next(); //=> {value: 3, done: false}
iterator.next(); //=> {value: undefined, done: true}
3.2. yield
yield
关键字是ES6新增的,
指定了迭代器在被 next()
方法调用时应当按顺序返回的值。
生成器函数会在每个 yield
语句后停止执行。
例如,此代码中 yield 1
执行后,该函数将不再执行任何操作,
直到迭代器的 next()
方法被调用,此时才继续执行 yield 2
。
function *createIterator( items ) {
for ( let i = 0; i < items.length; i++ ) {
yield items[ i ];
}
}
let iterator = createIterator( [ 1, 2, 3 ] );
iterator.next(); //=> {value: 1, done: false}
iterator.next(); //=> {value: 2, done: false}
iterator.next(); //=> {value: 3, done: false}
iterator.next(); //=> {value: undefined, done: true}
生成器函数也是函数,能被用于可用函数的位置。
yield
无法用在嵌套函数内。
3.3. 生成器函数表达式
let iterator = function *( items ) {
for ( let i = 0; i < items.length; i++ ) {
yield items[ i ];
}
}
3.4. 生成器对象方法
由于生成器就是函数,因此也可以被添加到对象中。
var o = {
createIterator: function *( items ) {
for ( let i = 0; i < items.length; i++ ) {
yield items[ i ];
}
}
};
也可以使用ES6方法简写方式:
var o = {
*createIterator( items ) {
for ( let i = 0; i < items.length; i++ ) {
yield items[ i ];
}
}
}
4. 可迭代对象与 for-of
可迭代对象(iterable)是包含 Symbol.iterator
属性的对象。
可通过 Symbol.iterator
这个属性可以获取指定对象的默认迭代器函数。
ES6中,所有的集合对象(数组、Set、Map、字符串)都是可迭代对象。
let values = [ 1, 2, 3 ];
for (let num of values) {
console.info( num );
}
for-of
循环在循环每次执行时会调用可迭代对象的 next()
方法,
并将结果对象的value
属性的值存储在一个变量上。
循环过程会持续到结果对象的done
属性变成true
为止。
4.1. 访问默认迭代器
可使用 Symbol.iterator
属性来访问对象上的默认迭代器:
let values = [ 1, 2, 3 ];
let iterator = values[ Symbol.iterator ]();
iterator.next(); //=> {value: 1, done: false}
iterator.next(); //=> {value: 2, done: false}
iterator.next(); //=> {value: 3, done: false}
iterator.next(); //=> {value: undefined, done: true}
也可使用 Symbol.iterator
来检测一个对象是否能进行迭代:
let str = "Hello";
typeof str[ Symbol.iterator ] === "function"; //=> true
let set = new WeakSet();
typeof set[ Symbol.iterator ] === "function"; //=> false
4.2. 创建可迭代对象
给对象设置Symbol.iterator
属性,该属性的值为迭代器函数,则此对象就可迭代了。
let collection = {
items: [],
[Symbol.iterator]: function *() {
for ( let item of this.items ) {
yield item;
}
}
};
collection.items.push( 1 );
collection.items.push( 2 );
collection.items.push( 3 );
for ( let val of collection ) {
console.info( val );
}
5. 内置的迭代器
迭代器是ES6的一个重要部分,你无需为许多内置类型创建你自己的迭代器,
语言已经默认包含它们了。
只有当内置的迭代器无法满足你的需要时,才有必要创建自定义的迭代器。
5.1. 集合的迭代器
ES6具有三种集合对象类型:数组、Map、Set。都有如下迭代器:
entries()
:返回一个包含键值对的迭代器。values()
:…集合中的值…。keys()
:…集合中键…。
当 for-of
循环没有显式指定迭代器时,
每种集合类型都有一个默认的迭代器供循环使用。
- Set:values()
- Map:entries()
5.2. NodeList 的迭代器
文档对象模型(DOM)具有一种 NodeList
类型,
用于表示页面文档中的元素的集合。
DOM关于NodeList
的规定(HTML规范)也包含了一个默认迭代器。这意味着可以使用 for-of
。
var divs = document.getElementByTagName( "div" );
for ( let div of divs ) {
console.info( div.id );
}
5.3. 扩展运算符
由于扩展运算符能用在任意可迭代对象上,
它就成为了将可迭代对象转换为数组的最简单方法。
let map = new Map( [
[ "name", "吴钦飞" ],
[ "gender", "男" ]
] );
[ ...map ];
//=> [["name","吴钦飞"],["gender","男"]]