5.8.5 迭代器方法
集合的
entries/keys/values与Symbol.iterator都返回迭代器,代理它们既要包装返回值,也要追踪迭代依赖。
一、集合的迭代器
const m = new Map([['key1', 'value1'], ['key2', 'value2']])
for (const [key, value] of m.entries()) {
console.log(key, value) // key1 value1 / key2 value2
}
// Map、Set 自身部署了 Symbol.iterator,可直接 for...of
for (const [key, value] of m) { /* 同上 */ }
// 也可以手动取迭代器
const itr = m[Symbol.iterator]()
itr.next() // { value: [ 'key1', 'value1' ], done: false }
console.log(m[Symbol.iterator] === m.entries) // true
二、问题:代理对象不可迭代
const p = reactive(new Map([['key', 1]]))
for (const [key, value] of p) { /* TypeError: p is not iterable */ }
原因:for...of 内部会读取 p[Symbol.iterator],该操作会触发 get 拦截函数;而 Symbol.iterator 只是在原始对象上实现,代理对象上取不到可用的实现。
解决:把迭代器方法的实现放进 mutableInstrumentations:
const mutableInstrumentations = {
// p.entries 与 p[Symbol.iterator] 等价,可共用同一实现
[Symbol.iterator]: iterationMethod,
entries: iterationMethod,
values: valuesIterationMethod,
keys: keysIterationMethod
}
三、iterationMethod:包装返回值 + 追踪依赖
function iterationMethod() {
const target = this.raw
// 获取原始迭代器方法
const itr = target[Symbol.iterator]()
const wrap = (val) => (typeof val === 'object' && val !== null ? reactive(val) : val)
// 迭代操作与元素数量有关,size 变化就应重新执行
track(target, ITERATE_KEY)
return {
next() {
const { value, done } = itr.next()
return {
// 迭代产出的值若不是 undefined,则包装成响应式数据
value: value ? [wrap(value[0]), wrap(value[1])] : value,
done
}
},
// 实现可迭代协议
[Symbol.iterator]() {
return this
}
}
}
要点:
- 包装:迭代产出的键值对如果是可代理的数据,要包装成响应式数据;
- 追踪:
track(target, ITERATE_KEY)—— 只要集合的元素数量变化,迭代操作就应重新执行。
四、两种协议别混淆
| 协议 | 要求实现 | 说明 |
|---|---|---|
| 可迭代协议 | [Symbol.iterator]() | 对象可被 for...of 迭代 |
| 迭代器协议 | next() | 对象本身是迭代器 |
一个对象可以同时实现两者:
const obj = {
[Symbol.iterator]() { return this }, // 可迭代协议
next() { /* ... */ } // 迭代器协议
}
p.entries的返回值带有next但没有Symbol.iterator,所以它只是迭代器、不是可迭代对象;这也是自定义迭代器要补上[Symbol.iterator]() { return this }的原因。
相关
- 5.8.4 处理forEach:遍历与
ITERATE_KEY的联系 - 5.8.6 values与keys方法:
keys为什么用另一个 key 追踪