能够将可迭代对象理解为“宽泛意义上的数组”——就是说,不必定是数组(Array.isArray(iterable) 返回 false),但却可以被 for...of 循环遍历。java
概览
可迭代对象不必定是数组,数组必定是可迭代对象。
每一个可迭代对象必然包含一个 [Symbol.iterator] 方法属性
字符串也是可迭代对象
改造普通对象
咱们举一个例子,下面有一个对象:数组
let range = {
from: 1,
to: 5
};
// 咱们想用 for..of 遍历 range,获得从 1(from) 到 5(to) 的天然数
// for(let num of range) { consol.log(num) } // 遍历结果 1 -> 2 -> 3 -> 4 -> 5
复制代码
明眼人一看,就知道 range 不就是个普通对象嘛,跟可迭代对象有什么关系呢?还要用 for..of 遍历,遍历结果还要是 1 -> 2 -> 3 -> 4 -> 5,这不扯淡呢吗?说的对,如今确定是扯淡,那是由于咱们啥都没作呢,可不是扯淡吗?函数
为了能让 range 这个普通对象变为可迭代对象,咱们须要先来了解下 Symbol.iterator 这个系统预置变量。ui
Symbol.iterator
咱们先来看下,这个属性的描述是怎样的。this
Object.getOwnPropertyDescriptor(Symbol, 'iterator')
// {
// value: Symbol(Symbol.iterator),
// writable: false,
// enumerable: false,
// configurable: false
// }
复制代码
由结果可知,这个属性咱们是修改不了的。spa
下面咱们为上面的 range 对象添加一个 [Symbol.iterator] 方法属性——对的,这个属性的属性值是函数,每一个可迭代对象都有一个 [Symbol.iterator] 方法属性,没有的话,确定不是可迭代对象。code
let range = {
from: 1,
to: 5
}
// 1. for..of 循环首先会调用对象上的 [Symbol.iterator] 属性——range[Symbol.iterator](),
// 属性 range[Symbol.iterator] 称为“迭代对象生成器”或“迭代对象生成函数”
range[Symbol.iterator] = function(){
// range[Symbol.iterator]() 的调用结果,会返回一个包含 next 方法的对象,
// 这个对象称为“迭代对象”
// 2. 接下来, for..of 就是彻底在跟这个迭代对象打交道了
return {
current: this.from,
last: this.to,
// 3. 每次 for..of 循环一次,就要调用一次 next 方法
next() {
// 4. 从 next 方法返回的对象中,咱们能得到当前遍历的值(value)以及遍历是否结束的标记(done)
if (this.current <= this.last) {
return { done: false, value: this.current++ }
} else {
return { done: true }
}
}
}
}
复制代码
for...of 循环遍历的本质是:cdn
for..of 循环首先会调用对象上的方法属性 [Symbol.iterator]——range[Symbol.iterator](),获得一个包含 next 方法的对象。
这个包含 next 方法的对象称为迭代对象(iterator object)
属性 range[Symbol.iterator] 被称为迭代对象生成器或迭代对象生成函数
接下来, for..of 就是彻底在跟这个迭代对象打交道了,
每次 for..of 循环一次,就要调用一次 next 方法,
从 next 方法返回的对象中({ done: ..., value: ... }),咱们能得到当前遍历的值(value)以及遍历是否结束的标记(done)。
通过上面的叙述,咱们还能够将可迭代对象定义为:可以生成“迭代对象”的对象。
** 对象
遍历改造对象
如今对改造后的 range 对象进行遍历。
let range = {
from: 1,
to: 5
}
range[Symbol.iterator] = function(){...}
for (let num of range) {
console.log(num) // 1, 而后是 2, 3, 4, 5
}
复制代码
很酷啊,如今能够一次遍历出 1 -> 2 -> 3 -> 4 -> 5 这 5 个数字了。
手动遍历可迭代对象
由于好玩,我们模仿 for...of 循环内部执行流程,纯手工写一下遍历可迭代对象的逻辑吧。
这里会用到 while 循环:
// 下面的写法,等同于
// for (let num of range) { console.log(num) };
let iterator = range[Symbol.iterator]();
while (true) {
let result = iterator.next();
// 标记结束(done 为 true),就终止循环,结束遍历
if (result.done) break;
// 不然,打印当前遍历的值
console.log(result.value);
}
复制代码
首先,手动调用 range[Symbol.iterator] 方法,得迭代对象
在 while 循环内部:
若是标记结束(done 为 true),就终止循环,结束遍历
不然,打印当前遍历的值
内置可迭代对象
前面咱们说过:可迭代对象不必定是数组,如今再加一句:数组必定是可迭代对象。根据经验,咱们知道数组是能够用 for...of 循环遍历的。
数组
遍历 symbols 被 for...of 循环成功遍历了。这是意料之中的事情,但咱们再来看下,这个数组对象里是否是有个叫 Symbol.iterator 的属性 🕵️♂️
果真!
字符串
字符串也是可迭代对象。证据以下:
再来找找 Symbol.iterator 属性。
Map 和 Set
Map 和 Set 也是可以被 for...of 遍历的。在这里就很少举例了,直接展现它们各自部署的 Symbol.iterator 属性。
从上面的截图里,咱们能够总结出一点内容来:
Map 对象默认的迭代对象生成器函数是 map.entries(),而
Set 对象默认的迭代对象生成器函数是 map.values()
更多关于 Map 和 Set 对象的遍历内容,请参考《遍历 Map 和 Set》。
(完)