Iterable
Iterable 是能提供默认迭代器的对象,是 for...of 和展开运算符的输入前提。
#type / concept
#status / growing
#resource / javascript
#resource / ecmascript
[!info] related notes
- 所属 MOC: ES6 新特性 MOC, ECMAScript MOC
- 前置概念: Symbol, ECMAScript迭代器和生成器
- 并列概念: Iterator, Generator
- 关系笔记: Iterable、Iterator、Generator 关系
Iterable
一句话定义
Iterable 是实现了 Symbol.iterator 方法的对象,该方法返回一个迭代器(Iterator),使得对象可以被 for...of、展开运算符等语法消费。
核心机制 / 工作原理
迭代协议:Iterable 与 Iterator
两个协议紧密关联但不同:
Iterable 协议(可迭代协议):
- 对象必须实现
[Symbol.iterator]()方法 - 该方法返回一个 Iterator 对象
Iterator 协议(迭代器协议):
- 返回的对象必须有
next()方法 next()返回{ value: any, done: boolean }done: true表示迭代结束
// 手动实现一个可迭代对象
const range = {
from: 1,
to: 5,
[Symbol.iterator]() {
let current = this.from
const last = this.to
return {
next() {
if (current <= last) {
return { value: current++, done: false }
}
return { done: true }
}
}
}
}
for (const num of range) {
console.log(num) // 1, 2, 3, 4, 5
}
内置可迭代对象
| 类型 | 说明 |
|---|---|
Array | 最常见的可迭代对象 |
String | 字符串可按 Unicode 码点迭代 |
Map | 迭代 [key, value] 对 |
Set | 迭代每个元素 |
TypedArray | 如 Uint8Array、Float32Array |
arguments | 函数参数列表 |
NodeList | DOM 查询结果 |
ReadableStream | 流式数据(异步可迭代) |
消费 iterable 的语法
const arr = [1, 2, 3]
// for...of
for (const item of arr) { }
// 展开运算符
const copy = [...arr]
// 解构赋值
const [a, b] = arr
// Array.from()
const doubled = Array.from(arr, x => x * 2)
// Map / Set 构造函数
const set = new Set(arr)
// Promise.all()
await Promise.all(arr.map(asyncFn))
自定义可迭代对象
使用生成器函数更简洁:
const range = {
from: 1,
to: 5,
*[Symbol.iterator]() {
for (let i = this.from; i <= this.to; i++) {
yield i
}
}
}
// 现在 range 可以被 for...of 消费
[...range] // [1, 2, 3, 4, 5]
生成器函数创建迭代器
function* fibonacci() {
let a = 0, b = 1
while (true) {
yield a
;[a, b] = [b, a + b]
}
}
// fibonacci() 返回一个既是迭代器又是可迭代对象的生成器
const fib = fibonacci()
fib.next() // { value: 0, done: false }
fib.next() // { value: 1, done: false }
// 生成器也是可迭代的
for (const n of fibonacci()) {
if (n > 100) break
console.log(n) // 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89
}
最小例子
// 最简单的可迭代对象
const iterable = {
[Symbol.iterator]() {
let i = 0
return {
next: () => ({ value: i++, done: i > 3 })
}
}
}
[...iterable] // [0, 1, 2]
与类数组对象的区别
| 维度 | Iterable | 类数组(Array-like) |
|---|---|---|
| 定义 | 实现了 Symbol.iterator | 有 length 属性和数字索引 |
能用 for...of | 能 | 不能(除非同时是 iterable) |
| 能用展开运算符 | 能 | 不能 |
| 转数组 | [...iterable] | Array.from(arrayLike) |
| 典型例子 | Map、Set、NodeList | arguments、document.querySelectorAll |
// 类数组但不是 iterable
const arrayLike = { 0: 'a', 1: 'b', length: 2 }
// for (const x of arrayLike) {} // TypeError!
// 转换方式
Array.from(arrayLike) // ['a', 'b']
// NodeList 既是类数组又是 iterable
const nodes = document.querySelectorAll('div')
for (const node of nodes) { } // 可以
[...nodes] // 可以
边界与常见误解
- 误解:有
length就是可迭代的。 不是。length只是类数组的特征,可迭代需要Symbol.iterator。 - 误解:Object 是可迭代的。 普通对象
{}不是 iterable,不能用for...of。要用Object.keys/values/entries。 - 误解:
for...in和for...of一样。for...in遍历可枚举属性(含原型链),for...of遍历 iterable 的值。 - 边界:迭代器对象也可以是可迭代的。 如果迭代器的
next()方法的对象同时实现了Symbol.iterator(返回this),它就是 both。生成器就是这种设计。 - 边界:异步可迭代。
Symbol.asyncIterator+for await...of处理异步数据流。
最短记忆方式
- 有
Symbol.iterator→ 可迭代 → 能被for...of消费 - Iterator:有
next()返回{ value, done } - Iterable 不是 Iterator,但两者紧密关联