iterator[迭代器]
它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作。
通过调用迭代器的next()方法,可以依次获取集合中的每个元素,并返回一个包含 value 和 done 属性的对象。value表示当前元素的值,done表示是否已经遍历完所有元素。
- ES6 引入了新的遍历命令
for...of
,只要实现了迭代器接口,就可以使用for...of循环或者手动调用next()方法来进行遍历,该循环会自动去寻找 Iterator 接口。
使用for...of循环或者手动调用next()方法时运行过程:
- 首先,在需要进行遍历操作时,通过调用集合对象上的[Symbol.iterator]方法获取到该集合对象对应的默认迭代器。
- 然后,在每次调用next()方法时,迭代器会执行相应的操作,并返回一个包含 value 和 done 属性的对象。
- 如果 done 为 false ,则表示还有更多的元素需要遍历,此时 value 属性表示当前遍历到的值。
- 如果done为true,则表示已经遍历完所有元素,此时value属性为undefined。
let arr = ['a', 'b', 'c'];
let iterator = arr[Symbol.iterator]();
iterator.next(); // { value: 'a', done: false }
iterator.next(); // { value: 'b', done: false }
iterator.next(); // { value: 'c', done: false }
iterator.next(); // { value: undefined, done: true }
let arr = ['a', 'b', 'c'];
let iterator = arr[Symbol.iterator]();
iterator.next(); // { value: 'a', done: false }
iterator.next(); // { value: 'b', done: false }
iterator.next(); // { value: 'c', done: false }
iterator.next(); // { value: undefined, done: true }
使用generator函数实现iterator示例
let myIterable = {
[Symbol.iterator]: function* () {
yield 1;
yield 2;
yield 3;
}
}
[...myIterable] // [1, 2, 3]
let myIterable = {
[Symbol.iterator]: function* () {
yield 1;
yield 2;
yield 3;
}
}
[...myIterable] // [1, 2, 3]
为普通对象实现iterator接口
const obj = { a: 'A', b: 'B', c: 'C', d: 'D' }
obj[Symbol.iterator] = function* () {
let len = Object.values(this).length;
let length = Object.values(this).length;
while (len > 0) {
len--;
yield Object.values(this)[(length - 1) - len].toString();
}
return this
};
// 使用 for of
for ([i] of obj) {
console.log(i); // 'A', 'B', 'C', 'D'
}
// 拓展运算符
[...obj] // ['A', 'B', 'C', 'D']
const obj = { a: 'A', b: 'B', c: 'C', d: 'D' }
obj[Symbol.iterator] = function* () {
let len = Object.values(this).length;
let length = Object.values(this).length;
while (len > 0) {
len--;
yield Object.values(this)[(length - 1) - len].toString();
}
return this
};
// 使用 for of
for ([i] of obj) {
console.log(i); // 'A', 'B', 'C', 'D'
}
// 拓展运算符
[...obj] // ['A', 'B', 'C', 'D']
for of
- 一个数据结构只要部署了Symbol.iterator属性,就被视为具有 iterator 接口,就可以用for...of循环遍历它的成员。
for...of循环内部调用的是数据结构的Symbol.iterator方法。
- for...of循环可以使用的范围包括
数组
、Set
和Map
结构、类数组
的对象(比如arguments对象、DOM NodeList 对象)、Generator
对象,以及字符串
。
let arr = ["a", "b", "c", "d"]
for (let i of arr) {
console.log(i);
}
// a b c d
let str = 'HELLO'
for (let i of str) {
console.log(i);
}
// H E L L O
var engines = new Set(["Gecko", "Trident", "Webkit", "Webkit"]);
for (let i of engines) {
console.log(i);
}
// Gecko Trident Webkit
let mapData = new Map();
mapData.set("edition", 6);
mapData.set("committee", "TC39");
mapData.set("standard", "ECMA-262");
for (let [key, value] of mapData) {
console.log(key + ": " + value);
}
// edition: 6
// committee: TC39
// standard: ECMA-262
let arr = ["a", "b", "c", "d"]
for (let i of arr) {
console.log(i);
}
// a b c d
let str = 'HELLO'
for (let i of str) {
console.log(i);
}
// H E L L O
var engines = new Set(["Gecko", "Trident", "Webkit", "Webkit"]);
for (let i of engines) {
console.log(i);
}
// Gecko Trident Webkit
let mapData = new Map();
mapData.set("edition", 6);
mapData.set("committee", "TC39");
mapData.set("standard", "ECMA-262");
for (let [key, value] of mapData) {
console.log(key + ": " + value);
}
// edition: 6
// committee: TC39
// standard: ECMA-262
for of、for in、forEach
for of、for in、forEach 比较
for of
:
- 用于遍历部署了[Symbol.iterator]接口的对象。如:
数组
、Set
、Map
、类数组
、Generator
以及字符串
。 - for of 遍历 能正确识别 Unicode 字符。
- 无法直接遍历普通对象。
- 可以正确响应
break
、continue
和return
语句(都是终止循环)break、return 是终止循环,continue是跳出当前循环,进入下一次循环。
for in
:
- 该循环用于遍历对象的可枚举属性,包括继承的属性。它迭代对象的
键
,而非值。 - for in 遍历的是对象的属性,遍历的结果顺序是不确定的。
- 数字的键名,会被转为 String 类型。
- 可以正确响应
break
、continue
和return
语句(都是终止循环)break、return 是终止循环,continue是跳出当前循环,进入下一次循环。
forEach
:
- 它是数组的一个内置循环方法。
- 无法使用
break
、continue
、return
终止循环,只能通过抛出异常跳出循环。
[迭代器] 中的 return(),throw()
- 遍历器对象除了具有
next()
方法,还可以具有return()
方法和throw()
方法。 - 如果自己写遍历器对象生成函数,那么
next()
方法是必须部署的,return()
方法和throw()
方法是否部署是可选的。
for...of循环提前退出,通常是因为出错,或者有break语句--此时就会调用return()方法。
注意:return()
方法必须返回一个对象,这是 Generator 规格决定的。
注意:throw方法主要是配合 Generator 函数使用,一般的遍历器对象用不到这个方法。
拓展
迭代器对象(Iterator):
迭代器对象是具有next方法的对象。next方法用于逐个访问集合中的元素。每次调用next方法,它都会返回一个包含value和done属性的对象。
value表示当前元素的值,而done是一个布尔值,表示是否已经遍历完了所有元素,当done为true 表示遍历完成。
可迭代对象(Iterable):
可迭代对象是那些具有
[Symbol.iterator]
方法的对象,该方法返回一个迭代器对象。常见的可迭代对象包括数组、字符串、Map、Set等。
迭代协议
- 迭代协议并不是新的内置实现或语法,而是协议。这些协议可以被任何遵循某些约定的对象来实现。
- 迭代协议具体分为两个协议:
可迭代协议
和迭代器协议
。
可迭代协议
可迭代协议允许 JavaScript 对象定义或定制它们的迭代行为,例如,在一个 for..of 结构中,哪些值可以被遍历到。一些内置类型同时是内置的可迭代对象,并且有默认的迭代行为,比如 Array 或者 Map,而其他内置类型则不是(比如 Object)。
要成为可迭代对象,该对象必须实现
[Symbol.iterator]()
方法,这意味着对象(或者它原型链上的某个对象)必须有一个键为[Symbol.iterator]
的属性,可通过常量 Symbol.iterator 访问该属性:[Symbol.iterator]:一个无参数的函数,其返回值为一个符合迭代器协议的对象。
当一个对象需要被迭代的时候(比如被置入一个 for...of 循环时),首先,会不带参数调用它的
[Symbol.iterator]()
方法,然后使用此方法返回的迭代器获得要迭代的值。值得注意的是调用此无参数函数时,它将作为对可迭代对象的方法进行调用。因此,在函数内部,this 关键字可用于访问可迭代对象的属性,以决定在迭代过程中提供什么。
此函数可以是普通函数,也可以是生成器函数,以便在调用时返回迭代器对象。在此生成器函数的内部,可以使用 yield 提供每个条目。
迭代器协议
- 迭代器协议定义了产生一系列值(无论是有限个还是无限个)的标准方式,当值为有限个时,所有的值都被迭代完毕后,则会返回一个默认返回值。
- 只有实现了一个拥有以下语义(semantic)的 next() 方法,一个对象才能成为迭代器:
next():无参数或者接受一个参数的函数,并返回符合 IteratorResult 接口的对象(见下文)。如果在使用迭代器内置的语言特征(例如 for...of)时,得到一个非对象返回值(例如 false 或 undefined),将会抛出 TypeError("iterator.next() returned a non-object value")。
- 所有迭代器协议的方法(next()、return() 和 throw())都应返回实现 IteratorResult 接口的对象。它必须有以下属性:
done[可选]
:如果迭代器能够生成序列中的下一个值,则返回 false 布尔值。(这等价于没有指定 done 这个属性。)
如果迭代器能够生成序列中的下一个值,则返回 false 布尔值。(这等价于没有指定 done 这个属性。)
value[可选]
:迭代器返回的任何 JavaScript 值。done 为 true 时可省略。
- 实际上,两者都不是严格要求的;如果返回没有任何属性的对象,则实际上等价于 { done: false, value: undefined }。
- 如果一个迭代器返回一个 done: true 的结果,则对任何 next() 的后续调用也返回 done: true,尽管这在语言层面不是强制的。
- next 方法可以接受一个值,该值将提供给方法体。任何内置的语言特征都将不会传递任何值。传递给生成器 next 方法的值将成为相应 yield 表达式的值。
- 可选地,迭代器也实现了
return(value)
和throw(exception)
方法,这些方法在调用时告诉迭代器,调用者已经完成迭代,并且可以执行任何必要的清理(例如关闭数据库连接)。return(value)[可选]
:无参数或者接受一个参数的函数,并返回符合 IteratorResult 接口的对象,其 value 通常等价于传递的 value,并且 done 等于 true。调用这个方法表明迭代器的调用者不打算调用更多的 next(),并且可以进行清理工作。
throw(exception)[可选]
:无参数或者接受一个参数的函数,并返回符合 IteratorResult 接口的对象,通常 done 等于 true。调用这个方法表明迭代器的调用者监测到错误的状况,并且 exception 通常是一个 Error 实例。
很容易使一个迭代器也可迭代:只需实现 [Symbol.iterator]()
方法,并返回它的 this。
const myIterator = {
next() { /**... */ },
[Symbol.iterator]() {
return this;
},
};
const myIterator = {
next() { /**... */ },
[Symbol.iterator]() {
return this;
},
};
异步迭代器和异步可迭代协议
- 用于异步迭代的协议,命名为异步迭代器和异步可迭代协议。它们与可迭代和迭代器协议有着非常类似的接口,只是从调用迭代器方法的每个返回值都包装在一个 promise 中。
- 当对象实现以下方法时,它会实现异步可迭代协议:
[Symbol.asyncIterator]
:返回对象的无参数函数,并且符合异步迭代器协议。 - 当对象实现以下方法时,它会实现异步迭代器协议:
next()
:无参数或者接受一个参数的函数,并返回 promise。promise 兑现为一个对象,该对象符合 IteratorResult 接口,并且这些属性与同步迭代器有着相同的语义。return(value)[可选]
:无参数或者接受一个参数的函数,并返回 promise。promise 兑现为一个对象,该对象符合 IteratorResult 接口,并且这些属性与同步迭代器有着相同的语义。throw(exception)[可选]
:无参数或者接受一个参数的函数,并返回 promise。promise 兑现为一个对象,该对象符合 IteratorResult 接口,并且这些属性与同步迭代器有着相同的语义。
语言和迭代协议之间的交互
JavaScript 语言指定了产生或使用可迭代对象和迭代器的 API。
内置的可迭代对象:
- String、Array、TypedArray、Map、Set 以及 Intl.Segments 都是内置的可迭代对象,因为它们的每个 prototype 对象都实现了
[Symbol.iterator]()
方法。 - 此外,arguments 对象和一些 DOM 集合类型,如 NodeList 也是可迭代的。目前,没有内置的异步可迭代对象。
生成器函数返回生成器对象,它们是可迭代的迭代器。异步生成器函数返回异步生成器对象,它们是异步可迭代的迭代器。
从内置迭代返回的迭代器实际上都继承了一个公共类(目前尚未暴露),该类实现了上述 [Symbol.iterator]() { return this; }
方法,使它们都是可迭代的迭代器。将来,除了迭代器协议要求的 next() 方法外,这些内置迭代器可能还有其他辅助方法。你可以通过在图形控制台中记录迭代器的原型链来检查它。
接受可迭代对象的内置 API
- Map()
- WeakMap()
- Set()
- WeakSet()
- Promise.all()
- Promise.allSettled()
- Promise.race()
- Promise.any()
- Array.from()
- Object.groupBy()
- Map.groupBy()