我们在很前面的时候就讲到了迭代器这么一个东西。那么他究竟是什么呢?又有什么样的作用呢?本节我们就来讲述 Iterables 与 Iterators。也就是可迭代性与迭代器。
概述
ES6 中新增了一个迭代的接口,叫做可迭代性(Iterable )。本节呢,将要向大家讲述它是怎么工作的,以及它运用于那些 ECMAScript 语言类型中。
我们来看看迭代(Iteration )是什么,它分为两个部分:
- Iterable:可迭代性是一种数据结构,它希望使其元素可以访问公共部分。它通过内置系统的一个方法,键为 Symbol.iterator。这个方法就是迭代器的工厂。
- Iterator:用于遍历数据结构的元素的指针。
以下的值具有可迭代性:
- Array
- String
- Map
- Set
- DOM 数据结构(在程序中工作的那部分)
那么具有可迭代性的对象可以做哪些事呢?如下所示:
(1)解构
let [a,b] = new Set(['a', 'b']);
console.log(a); // "a"
console.log(b); // "b"
(2)for-of 循环
for (let x of ['a', 'b', 'c']) {
console.log(x);
}
// "a"
// "b"
// "c"
(3)Array.from( ) 方法
let arr = Array.from(new Set(['a', 'b', 'c']));
// ["a", "b", "c"]
(4)展开操作符(...)
let arr = [...new Set(['a', 'b', 'c'])];
// ["a", "b", "c"]
(5)Map 和 Set 构造函数
let map = new Map([
[1,'a'],
[2,'b']
]);
let set = new Set(['a', 'b', 'c']);
(6)Promise.all( ) 、Promise.race( ) 方法
Promise.all(iterableOverPromises).then(···);
Promise.race(iterableOverPromises).then(···);
(7)yeild()*
yield* anIterable;
看完以上的这些,记住了,基本上这节内容就大致清楚了。
可迭代性
这里我们将要讲到可迭代性的思想,也就是说,可迭代到底是什么?
可迭代性的思想:
- 数据消费者(Data consumers ):JavaScript 具有“消费”数据的语言结构。例如,for-of 循环用于遍历值,而扩展运算符(...)将值插入到数组或函数调用中。
- 数据源(Data Sources):数据消费者可以从各种来源获取值。例如,您可能需要遍历数组的元素,或者Map 中的键值(使用 entries 方法)以及 String 实例中的字符等。
我们这里必须清楚,想要每个数据消费者都能获取数据源,这是不切实际的。尤其是当我们创建新的源时(例如通过一个库)。因此,ES6 就提供了 Iterable 的接口。数据消费者使用它,而数据源负责实现它。我们来看下图:
这里有两个知识:
- 源(Source ):如果一个键拥有 Symbol.Iterator 方法并返回迭代器时,它的值就被认为是可迭代的。迭代器是一个对象,它可以使用 next( ) 方法返回值。我们可以这样说:这个方法每次使用时,都可以枚举值。
- 消耗(Consumption ):数据使用者使用迭代器来检索它们消耗的值。
这里我们来举个之前的例子吧:
let set = new Set(['a','b','c']);
let keys = set.keys();
console.log(keys.next()); // {value: "a", done: false}
console.log(keys.next()); // {value: "b", done: false}
console.log(keys.next()); // {value: "c", done: false}
console.log(keys.next()); // {value: undefined, done: true}
上述示例中,我们使用了 keys() 方法与 next() 方法来进行该 Set 实例每个值的枚举与输出。就是因为 Set 拥有 Symbol.Iterator 方法。因此我们还可以使用下述的方式进行遍历:
let set = new Set(['a','b','c']);
let iter = set[Symbol.iterator]();
console.log(iter.next()); // {value: "a", done: false}
console.log(iter.next()); // {value: "b", done: false}
console.log(iter.next()); // {value: "c", done: false}
console.log(iter.next()); // {value: undefined, done: true}
输出结果一致,没有问题。
可以看到,next( ) 返回包装在对象中的每个项目,作为属性值的值。布尔属性 done 指示何时已达到项目序列的结束。
迭代和迭代器是所谓的协议(方法加上使用它们的规则)的一部分迭代。此协议的关键特性是它是顺序的:迭代器一次返回一个值。 这意味着如果一个可迭代的数据结构是非线性的(如一颗树),迭代将会使其线性化。
迭代的数据源
在概述中我举了几个例子。以下我使用 for-of 循环来迭代每个类型。我们一起来看下输出的值是什么:
(1)Array
for (let x of ['a', 'b']) {
console.log(x);
}
// "a"
// "b"
**(2)String **
for (let x of 'Hello') {
console.log(x);
}
// "H"
// "e"
// "l"
// "l"
// "e"
**(3)Map **
let map = new Map()
.set('a', 1)
.set('b', 2);
for (let pair of map) {
console.log(pair);
}
// ["a", 1]
// ["b", 2]
**(4)Set **
let set = new Set()
.add('a')
.add('b');
for (let x of set) {
console.log(x);
}
// "a"
// "b"
(5)arguments
function printArgs() {
for (let x of arguments) {
console.log(x);
}
}
printArgs('a', 'b');
// "a"
// "b"
(6)DOM 数据结构
for (let node of document.querySelectorAll('div')) {
// ···
}
现在更加清晰了吧?举出这么多例子的目的就是希望大家熟悉它。
迭代计算的数据
不是所有可迭代内容都必须来自数据结构,它也可以即时计算。
例如我们前面学习的 Array、Map、Set 都拥有 entries( )、keys( )、values( ) 三个方法。它们都是即时计算实例中的内容,再进行输出。
- entries( ) 方法返回实例的 [key, value] 的数组。
- keys( ) 方法返回实例的键。
- values( ) 方法返回实例的值。
但是,我们需要知道,Object 类型是没有迭代性可言的。因此它没有 for-of 循环,只有 for-in 的遍历。当然,在未来可能会有内置方法。
// 错误
for (let x of {a: 1, b: 2}) { // TypeError
console.log(x);
}
总结
本节把迭代的知识点讲完了,但是具体的用法还是比较多的。需要我们不断地实践。当你忘记的时候,就回来看看,将这些可迭代的对象牢记在脑海中,在使用时,就不会出错。