Iterator
是一种接口,为各种不同的数据结构提供统一访问的机制(使用 for...of
循环)。任何数据结构只要部署了 Iterator
接口,就可以完成遍历操作。
Iterator
的作用有三个:
ES6
新增的遍历命令 for...of
消费。Iterator
遍历的过程是这样的:
next()
方法,将指针指向数据结构的第一个成员,该方法返回一个结构为 {value: any, done: boolean }
的对象,表示当前数据成员的信息;next()
方法,直至它指向数据结构的结束位置。使用函数模拟 Iterator
机制如下:
function generateIterator(arr: any[]) {
let index = 0
return {
next() {
if (index < arr.length) {
const value = arr[index]
index++
return {
value,
done: false
}
} else {
return { value: undefined, done: true }
}
}
}
}
const gi = generateIterator([1, 3, 2])
console.log(gi.next()) // { value: 1, done: false }
console.log(gi.next()) // { value: 3, done: false }
console.log(gi.next()) // { value: 2, done: false }
console.log(gi.next()) // { value: undefined, done: true }
console.log(gi.next()) // { value: undefined, done: true }
ES6
规定,默认的 Iterator
接口部署在数据结构的 Symbol.iterator
属性,具有 Symbol.iterator
属性就是部署了 Iterator
接口,就可遍历。
Symbol.iterator
属性本身是一个函数,就是当前数据结构的遍历器生成函数 ,返回一个遍历器对象 Iterator
,该对象的根本特征是具有 next()
方法,每次调用 next()
方法都会返回一个代表当前成员的信息对象,用 TypeScript
来描述 遍历器接口(Iterable)
、 遍历器对象(Iterator)
和 next方法返回值(IterationResult)
的结构如下:
interface Iterable {
[Symbol.iterator](): Iterator
}
interface Iterator {
next(value?: any): IterationResult
}
interface IterationResult {
value: any
done: boolean
}
ES6
的有些数据结构默认可被 for...of
遍历(如数组),原因在于这些数据结构原生部署了 Symbol.iterator
属性:
let arr = [1, 2, 3]
let it = arr[Symbol.iterator]()
it.next() // { value: 1, done: false }
it.next() // { value: 2, done: false }
it.next() // { value: 3, done: false }
it.next() // { value: undefined, done: true }
其他数据结构如果想要能够被遍历,需要手动部署 Symbol.iterator
属性:
class IterableDemo {
constructor(public data: string[]) {}
[Symbol.iterator]() {
let index = 0
return {
next: () => {
if (index < this.data.length) {
return {
value: this.data[index++],
done: false
}
} else {
return {
value: undefined,
done: true
}
}
}
}
}
}
let iterableDemo = new IterableDemo(['a', 'b', 'c'])
for (const item of iterableDemo) {
console.log(item)
}
// a b c
Symbol.iterator
方法最简单的实现办法是使用 Generator
函数。
上面例子中的 [Symbol.iterator]
方法写起来十分繁琐,可以改写成 Generator函数
:
*[Symbol.iterator]() {
let index = 0
while (index < this.data.length) {
yield this.data[index++]
}
}
可以看到,几乎不用部署任何代码,只需要使用 yield
命令给出每一步的返回值即可。
对于类似数组的对象(存在数值键名和 length
属性),部署 Iterator
接口有一个简便方法 —— 使用数组的 Symbol.iterator
属性:
let obj = {
0: 'A',
1: 'B',
length: 2
}
;(obj as any)[Symbol.iterator] = Array.prototype[Symbol.iterator]
for (const item of obj as any) {
console.log(item)
}
// A B
字符串也是类数组的对象,也原生具有 Iterator
接口:
let str = 'ABC'
let itStr = str[Symbol.iterator]()
console.log(itStr.next()) // { value: 'A', done: false }
console.log(itStr.next()) // { value: 'B', done: false }
console.log(itStr.next()) // { value: 'C', done: false }
console.log(itStr.next()) // { value: undefined, done: true }
解构赋值
可遍历对象进行解构赋值时,会默认调用 Iterator
接口:
let [a, , b] = [1, 2, 3]
console.log(a, b) // 1 3
let [s, ...rest] = 'abcd'
console.log(s, rest) // a [ 'b', 'c', 'd' ]
拓展运算符
let arrx = [1, 2]
console.log([1, 2, ...arrx]) // [ 1, 2, 1, 2 ]
yield*
yield*
后面跟的是一个可遍历的结构,会调用其遍历器接口:
function* foo() {
yield 'x'
yield* [1, 2]
yield 'y'
}
console.log([...foo()]) // [ 'x', 1, 2, 'y' ]
其他场合
遍历器对象除了具有 next()
方法,还可以具有 return()
和 throw()
方法。
return()方法
如果 for...of
遍历过程中通过 break
或者由于错误而中途退出,就会调用 return()
方法,可以该方法来进行资源清理工作。
const iteratorReturnArr = [1, 3, 2, 3, 4, 5]
const itr = iteratorReturnArr[Symbol.iterator]()
iteratorReturnArr[Symbol.iterator] = () => {
itr.return = function() {
console.log('release memory')
return {
value: undefined,
done: true
}
}
return itr
}
for (const item of iteratorReturnArr) {
if (item % 2 === 0) {
break
}
console.log(item)
}
// 1
// 3
// release memory
for (const item of iteratorReturnArr) {
if (item % 2 === 0) {
throw new Error('even number')
}
console.log(item)
}
// 1
// 3
// release memory
// Error info...
throw()方法
throw()
方法主要配合 Generator函数
使用,一般的遍历器对象用不到该方法。
ES6
引入了 for...of
作为遍历所有数据结构的统一方法, for...of
内部调用的是数据结构的 Symbol.iterator
方法。
forEach
forEach()
方法无法中途跳出循环,break
命令或者 return
命令都无效;
而 for...of
可以中途跳出循环。
for…in
for...in
为遍历对象属性而设计,会遍历对象及其原型链上的所有键名,不适合遍历数组(如果遍历数组将返回数组的数字索引);
for...of
适用于遍历具有 Iterator
接口的数据结构。
总结:
for...of
用于遍历数组等可遍历对象,并且可以与break
,continue
,return
配合使用来灵活控制遍历过程。