可迭代对象与 for-of 循环
与迭代器紧密相关的是,可迭代对象(iterable
)是包含 Symbol.iterator
属性的对象。这个 Symbol.iterator
知名符号定义了为指定对象返回迭代器的函数。在 ES6
中,所有的集合对象(数组、Set 与 Map)以及字符串都是可迭代对象,因此它们都被指定了默认的迭代器。可迭代对象被设计用于与 ES
新增的 for-of
循环配合使用。
生成器创建的所有迭代器都是可迭代对象,因为生成器默认就会为 |
在本章开头我曾提到过在 for
循环中追踪索引的问题。迭代器是解决此问题的第一部分;for-of
循环则是第二部分:它完全删除了追踪集合索引的需要,让你无拘束地专注于操作集合内容。
for-of
循环在循环每次执行时会调用可迭代对象的 next()
方法,并将结果对象的 value
值存储在一个变量上。循环过程会持续到结果对象的 done
属性变成 true
为止。此处有个范例:
let values = [1, 2, 3];
for (let num of values) {
console.log(num);
}
此代码输出了如下内容:
1
2
3
这个 for-of
循环首先调用了 values
数组的 Symbol.iterator
方法,获取了一个迭代器(对 Symbol.iterator
的调用发生在 JS
引擎后台)。接下来 iterator.next()
被调用,迭代器结果对象的 value
属性被读出并放入了 num
变量。num
变量的值开始为 1
,接下来是 2
,最后变成 3
。当结果对象的 done
变成 true
,循环就退出了,因此 num
绝不会被赋值为 undefined
。
如果你只是简单地迭代数组或集合的值,那么使用 for-of
循环而不是 for
循环就是个好主意。for-of
循环一般不易出错,因为需要留意的条件更少;传统的 for
循环被保留用于处理更复杂的控制条件。
在不可迭代对象、 |
访问默认迭代器
你可以使用 Symbol.iterator
来访问对象上的默认迭代器,就像这样:
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 }"
此代码获取了 values
数组的默认迭代器,并用它来迭代数组中的项。这个过程与使用 for-of
循环时在后台发生的过程一致。
既然 Symbol.iterator
指定了默认迭代器,你就可以使用它来检测一个对象是否能进行迭代,正如下例:
function isIterable(object) {
return typeof object[Symbol.iterator] === "function";
}
console.log(isIterable([1, 2, 3])); // true
console.log(isIterable("Hello")); // true
console.log(isIterable(new Map())); // true
console.log(isIterable(new Set())); // true
console.log(isIterable(new WeakMap())); // false
console.log(isIterable(new WeakSet())); // false
这个 isIterable()
函数仅仅查看对象是否存在一个类型为函数的默认迭代器。for-of
循环在执行之前会做类似的检查。
本节至今的范例已经展示了在内置的可迭代类型上使用 Symbol.iterator
的方法,但还能用 Symbol.iterator
属性来创建你自己的可迭代对象。
创建可迭代对象
开发者自定义对象默认情况下不是可迭代对象,但你可以创建一个包含生成器的 Symbol.iterator
属性,让它们成为可迭代对象。例如:
let collection = {
items: [],
*[Symbol.iterator]() {
for (let item of this.items) {
yield item;
}
}
};
collection.items.push(1);
collection.items.push(2);
collection.items.push(3);
for (let x of collection) {
console.log(x);
}
此代码输出了如下内容:
1
2
3
本例首先为 collection
对象定义了一个默认的迭代器。这个默认迭代器是用 Symbol.iterator
方法创建的,此方法是一个生成器(注意名称之前依然有星号)。接下来该生成器使用了一个 for-of
循环来对 this.items
中的值进行迭代,并使用了 yield
来返回每个值。collection
对象依靠 this.items
的默认迭代器来工作,而非在定义的值上手动进行迭代。
本章后面的 “生成器委托” 描述了另一种方法,能使用另一个对象的迭代器。 |
现在你已经看到了数组默认迭代器的一些用法,但 ES6
中还内置了更多的迭代器,让处理数据集合更轻易。