for (let char of 'test') {
// 触发 4 次,每个字符一次
console.log(char); // t, then e, then s, then t
}
// 对于Unicode字符
for (let char of '𝒳😂') {
console.log(char); // 𝒳,然后是 😂
}
显式调用迭代器
const str = 'Hello';
// 和 for..of 做相同的事
// for (let char of str) console.log(char);
const iterator = str[Symbol.iterator]();
while (true) {
const result = iterator.next();
if (result.done) break;
console.log(result.value); // 一个接一个地输出字符
}
Iterable
/for...of
可迭代(Iterable) 对象是数组的泛化。这个概念是说任何对象都可以被定制为可在
for..of
循环中使用的对象。当使用for...of
循环遍历某种数据结构时,该循环会自动去寻找Iterator
接口。ES6 规定,默认的Iterator
接口部署在数据结构的Symbol.iterator
属性,下面将一个普通对象转化为可迭代对象:为了让
range
对象可迭代(也就让for..of
可以运行)我们需要为对象添加一个名为Symbol.iterator
的方法(一个专门用于使对象可迭代的内置 symbol)。for..of
循环启动时,它会调用这个方法(如果没找到,就会报错)。这个方法必须返回一个 迭代器(iterator) —— 一个有next
方法的对象。for..of
仅适用于这个被返回的对象。for..of
循环希望取得下一个数值,它就调用这个对象的next()
方法。next()
方法返回的结果的格式必须是{done: Boolean, value: any}
,当done=true
时,表示迭代结束,否则value
是下一个值。请注意可迭代对象的核心功能:关注点分离。
range
自身没有next()
方法。range[Symbol.iterator]()
创建了另一个对象,即所谓的“迭代器”对象,并且它的next
会为迭代生成值。因此,迭代器对象和与其进行迭代的对象是分开的。
从技术上说,我们可以将它们合并,并使用
range
自身作为迭代器来简化代码:现在
range[Symbol.iterator]()
返回的是range
对象自身:它包括了必需的next()
方法,并通过this.current
记忆了当前的迭代进程。这样更短,对吗?是的。有时这样也可以。但缺点是,现在不可能同时在对象上运行两个
for..of
循环了:它们将共享迭代状态,因为只有一个迭代器,即对象本身。但是两个并行的for..of
是很罕见的,即使在异步情况下。原生具备
Iterator
接口的数据结构如下:Array
Map
Set
String
TypedArray
arguments
对象NodeList
对象对于一个字符串,
for..of
遍历它的每个字符:显式调用迭代器
显式调用迭代器比使用
for..of
更能精细控制迭代过程。例如,我们可以拆分迭代过程:迭代一部分,然后停止,做一些其他处理,然后再恢复迭代。可迭代(iterable)和类数组(array-like)
有两个看起来很相似,但又有很大不同的正式术语。请你确保正确地掌握它们,以免造成混淆。
Symbol.iterator
方法的对象。length
属性的对象,所以它们看起来很像数组。实际任务中我们可能会遇到可迭代对象或类数组对象,或两者兼有。例如,字符串即是可迭代的(
for..of
对它们有效),又是类数组的(它们有数值索引和length
属性)。但是一个可迭代对象也许不是类数组对象。反之亦然,类数组对象可能不可迭代。可迭代对象和类数组对象通常都 不是数组,它们没有
push
和pop
等方法。如果我们有一个这样的对象,并想像数组那样操作它,就可以通过一些其他方法将其转化为数组。Array.from
另外用
Array.from
处理带 Unicode 的字符是非常方便的,与str.split
方法不同,它依赖于字符串的可迭代特性。可以基于
Array.from
创建 UTF-16 扩展字符的slice
方法:另外解构也是类似原理: