Open
Description
概念
遍历器(Iterator),一种为不同数据结构提供统一的访问机制的接口。
作用:
- 提供统一、简便的访问接口
- 使数据结构的成员能够按某种次序排列
- 供
for...of
遍历命令使用
规格描述
TypeScript 定义:
interface Iterable<T> {
[Symbol.iterator](): Iterator<T>;
}
interface Iterator<T, TReturn = any, TNext = undefined> {
// NOTE: 'next' is defined using a tuple to ensure we report the correct assignability errors in all places.
next(...args: [] | [TNext]): IteratorResult<T, TReturn>;
return?(value?: TReturn): IteratorResult<T, TReturn>;
throw?(e?: any): IteratorResult<T, TReturn>;
}
type IteratorResult<T, TReturn = any> = IteratorYieldResult<T> | IteratorReturnResult<TReturn>;
interface IteratorYieldResult<TYield> {
done?: false;
value: TYield;
}
interface IteratorReturnResult<TReturn> {
done: true;
value: TReturn;
}
只要有 [Symbol.iterator]
属性,就认为是“可遍历的”(iterable)
在 for...of
循环中,会调用 [Symbol.iterator]
,并执行其返回的 next
函数:
const iterable = {
[Symbol.iterator] () {
let i = 0
return {
next () {
return { value: i++, done: i > 5 }
}
}
}
}
for (let i of iterable) {
console.log(i)
}
// 0 1 2 3 4
const iter = iterable[Symbol.iterator]()
console.log(iter.next()) // { value: 0, done: false }
console.log(iter.next()) // { value: 1, done: false }
console.log(iter.next()) // { value: 2, done: false }
console.log(iter.next()) // { value: 3, done: false }
console.log(iter.next()) // { value: 4, done: false }
console.log(iter.next()) // { value: 5, done: true }
原生具备 Iterator 接口的数据结构:
- Array
- Map
- Set
- String
- TypedArray
- 函数的 arguments 对象
- NodeList 对象
原生数组 Iterator 接口示例:
const arr = [1, 2, 3]
const 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 }
调用 Iterator 接口的场合
- 解构赋值
- 扩展运算符(...)
- yield*
- 任何接受数组作为参数的场合,例如
for...of
,Array.from()
,Map()
,Set()
,WeakMap()
,WeakSet()
,Promise.all()
,Promise.race()
,Promise.allSettled()
Iterator 接口与 Generator 函数
见 Generator 笔记
return 与 throw
遍历器对象除了 next
,还可以有 return
与 throw
方法。这两个方法是可选的。
return
返回一个对象,在 for...of
循环提前退出(抛出错误或者手动 break
)时调用,可作为清理或释放资源用。
例如:
function readLinesSync (file) {
return {
[Symbol.iterator] () {
return {
next () {
return { done: false }
},
return () {
file.close()
return { done: true }
}
}
}
}
}
for (let line of readLinesSync(fileName)) {
console.log(line)
break
}
// 或者
for (let line of readLinesSync(fileName)) {
console.log(line)
throw new Error()
}
定义一个逐行读取文件的函数,在遍历完第一行后,如果是 break
或抛出错误了,则执行 return
方法,关闭文件。如果是抛出错误,会在 return
执行之后再抛出(个人未验证)
throw
方法主要配合 Generator 函数使用,见 Generator 笔记
for...of
与 for...in
for...of
循环作为遍历所有数据结构的统一的方法,只要部署了 Symbol.iterator
属性,就可以用 for...of
来遍历,因此,普通的对象不能用 for...of
遍历,只能使用 for...in
,而数组、Map、Set、arguments 对象等,就可以用 for...of
来遍历。
for...in
的缺点:
- 数组键名是数字,但遍历的时候是以字符串
'0'
,'1'
等作为键名 for...in
可能会遍历到原型链上的键for...in
不保证遍历的顺序
for (let key in [1, 2, 3]) {
console.log(typeof key) // string
}
const arr = [1, 2, 3]
Object.setPrototypeOf(arr, { protoProperty: 666 })
for (let key in arr) {
console.log(key)
}
// 0 1 2 protoProperty
总之 for...in
主要是为遍历对象而设计的,不适用于数组
另外提一下 forEach
这种函数遍历的方式,优点是简洁方便,但缺点也很明显:无法中途跳出循环