Iterator (遍历器)
-
是一种接口,为各种不同的数据结构提供统一的访问机制。主要供
for...of
消费。 -
每调用一次 next() 就会返回数据结构当前成员的信息:
{ value: '', done: false/true }
- value 当前成员的值。done 布尔值,遍历是否结束(即是否有必要再一次调用 next() 方法)。
- 一种数据结构只要是部署了 Iterator 接口,这种数据结构就是“可遍历的”
-
默认的 Iterator 接口部署在数据结构的 Symbol.iterator 属性,即只要用 Symbol.iterator 属性就可以认为是“可遍历的”。
-
Symbol.iterator 属性本身是一个函数。执行 Symbol.iterator 这个属性,会返回一个遍历器对象,该对象的根本特征就是具有 next 方法,每调用一次 next 方法就会返回一个当前成员的信息对象。
- 原生具备 Iterator 接口的数据结构有:Array、Map、Set、String、TypedArray、函数的 arguments 对象、NodeList 对象
对象是没有默认 Iterator 接口的,不能直接用 for...of 遍历。
本质上,遍历器是一种线性处理,对于任何非线性的数据接口,部署遍历器接口就等于部署一种线性转换。
- 对于
类似数组的对象
(存在数值键名和 length 属性),可以将 Symbol.iterator 方法直接引用数组的 Iterator 接口:
const obj = {
0: "a",
1: "b",
2: "c",
length: 3,
[Symbol.iterator]: Array.prototype[Symbol.iterator],
};
for (let i of obj) {
console.log(i);
}
// a
// b
// c
以上代码也可以用另一个简便的方法:使用
Array.from()
了解一下:Array.from() 将一个类似数组或者可迭代对象创建一个新的前拷贝的数组实例。
const obj = {
0: "a",
1: "b",
2: "c",
length: 3,
};
for (let i of Array.from(obj)) {
console.log(i);
}
普通对象部署数组的 Symbol.iterator 方法,并无效果。
调用 Iterator 接口的场合
-
解构赋值:对数组和 Set 结构进行解构赋值时会默认调用 Symbol.iterator 方法。
-
扩展运算符(...)也会默认调用 Iterator 接口。只要是部署了 Iterator 接口的数据结构,就可以使用扩展运算符将其转为数组。
const str = "abc";
console.log([...str]); // [ 'a', 'b', 'c' ]
-
yield* 后边跟的是一个可遍历的结构,它会调用该结构的 Iterator 接口。
-
接受数组作为参数的场合都调用了 Iterator 接口(因为数组的遍历会调用遍历器接口)。
- for...of
- Array.from()
- Map(), Set(), WeakMap(), WeakSet()(比如
new Map([['a',1],['b',2]])
) - Promise.all()
- Promise.race()
字符串的 Iterator 接口
字符串是一个类似数组的对象,也原生具有 Iterator 接口。
Iterator 接口与 Generator 函数
遍历器对象的 return() , throw()
遍历器对象除了具有 next() 还有 return() 和 throw() , return() 和 throw() 方法是否部署是可选的。
- return() 的使用场合:for...of 循环提前退出(通常是:抛出错误或者是 break / return 语句)就会调用 return() 方法。
-
抛出错误的情况:先执行 return() 方法,再抛出错误。
-
return() 必须返回一个对象,这是 Generator 语法决定的。
- throw() 方法主要是配合 Generator 函数使用,一般的遍历器对象用不到这个方法。
for...of
只要是有部署了 Symbol.iterator 属性的,都可以使用 for...of 来遍历其成员。
- 使用范围:数组、字符串、Set 和 Map 结构、某些类似数组的对象(如 arguments 对象、DOM NodeList 对象)、Generator 对象。
- 对于字符串来说, for...of 会正确识别 32 位 UTF-16 字符。
-
for...of 可以代替数组实例的 forEach() 方法
-
for...of 与 for...in 的区别:
- for...in 循环读取键名, for...of 循环读取键值。
const arr = ["a", "b", "c"];
// for...in
for (let i in arr) {
console.log(i);
}
// for...of
for (let i of arr) {
console.log(i);
}
- for...of 只返回具有数字索引的属性。
let arr = [3, 5, 7];
arr.foo = "hi";
// for...in
for (let i in arr) {
console.log(i); // "0", "1", "2", "foo"
}
// for...of
for (let i of arr) {
console.log(i); // "3", "5", "7"
}
- for...in 可以用来遍历对象的键名。for...of 不能直接用来遍历对象。
多种遍历语法的比较
-
for 循环
-
Array.forEach() 无法中途跳出循环,break 和 return 命令都不奏效。
-
for...in 遍历键名。主要是为遍历对象而设计的,不适用于遍历数组。缺点有:
-
数组的键名是数字,但是 for...in 循环是以字符串作为键名“0”、“1”、“2”等等。
-
for...in 循环不仅遍历数字键名,还会遍历手动添加的其他键,甚至包括原型链上的键。
-
某些情况下,for...in 循环会以任意顺序遍历键名。
- for...of 可以与 break、return 配合使用。提供了遍历所有数据结构的统一操作接口。