接下来,谈论下集合类型的迭代器方法。集合类型有三个迭代器方法:
entries
keys
value
调用这些方法会得到响应的迭代器,并且可以使用for…of进行循环迭代,例如:
const m = new Map([
['key1','value1'],
['key2','value2'],
])
for(const [key, value] of m.entries){
console.log(key,value)
}
Map或Set也可以直接使用for…of进行迭代
for(const [key, value] of m){
console.log(key,value)
}
当然也可以调用迭代器函数取得迭代器对象后,手动调用迭代器对象的next方法获取对应的值
const itr = m[Symbol.iterator]()
console.log(itr.next)
console.log(itr.next)
console.log(itr.next)
实际上,m[Symbol.iterator]与m.entries是等价的
了解上面这些知识后,再来看下面的例子:
const p = reactive(new Map([
['key1','value1'],
['key2','value2']
]))
effect(()=>{
// TypeError: p is not iterable
for(const [key,value] of p){
console.log(key,value)
}
})
p.set('key3','value3')
结果在遍历那步会报错,p是不可迭代的
原因在于p没有实现Symbol.iterator方法
实践中,使用for…of循环迭代一个代理对象,内部会试图从代理对象p上读取p[Symbol.iterator]属性,这个操作会触发get拦截函数,所以仍然把Symbol.iterator方法的实现放到mutableInstrumentations中,如下面代码:
const mutableInstrumentations = {
[Symbol.iterator](){
// 获取原始数据对象target
const target = this.raw
// 获取原始迭代器方法
const itr = target[Symbol.iterator]()
// 将其返回
return itr
}
}
把原始的迭代器对象返回就可以让for…of循环代理对象p了,但是还是有问题的,使用for…of循环迭代集合时,如果迭代产生的值也是可以被代理的,那么也应该将其包装称响应式数据
因此,需要修改代码:
const mutableInstrumentations = {
[Symbol.iterator](){
// 获取原始数据对象target
const target = this.raw
// 获取原始迭代器方法
const itr = target[Symbol.iterator]()
const wrap = (val) => typeof val === 'object' && val !== null ? reactive(val):val
// 返回自定义的迭代器
return {
next(){
// 调用原始迭代器的next方法获取value和done
const {value, done} = itr.next()
return{
// 如果value不是undefined,则对其进行包裹
value: value ? [wrap(value[0]),wrap(value[1])]:value,
done
}
}
}
}
}
如果值value不为undefined, 则对其进行包装,最后返回包装后的代理对象,这样当for…of循环进行迭代时,得到的值就会是响应式数据了。
最后为了追踪for…of对数据的迭代操作,还需要调用track函数,让副作用函数与ITERATE_KEY建立联系:
const mutableInstrumentations = {
[Symbol.iterator](){
const target = this.raw
const itr = target[Symbol.iterator]()
const wrap = (val) => typeof val === 'object' && val !== null ? reactive(val):val
// 调用track函数建立响应联系
track(target, ITERATE_KEY)
return {
next(){
// 调用原始迭代器的next方法获取value和done
const {value, done} = itr.next()
return{
// 如果value不是undefined,则对其进行包裹
value: value ? [wrap(value[0]),wrap(value[1])]:value,
done
}
}
}
}
}
由于迭代操作与集合中元素的数量有关,所以只要集合的size发生变化,就应该触发迭代操作重新执行。因此要调用track函数时让ITERATE_KEY与副作用函数建立联系。
const mutableInstrumentations = {
[Symbol.iterator](){
const target = this.raw
const itr = target[Symbol.iterator]()
const wrap = (val) => typeof val === 'object' && val !== null ? reactive(val) : val
// 调用track函数建立响应联系
track(target, ITERATE_KEY)
return {
next(){
const {value, done} = itr.next()
return {
value: value?[wrap(value[0]), wrap(value[1])]:value,
done
}
}
}
}
}
由于迭代器操作与集合中元素的数量有关,所以只要集合的size发生变化,就应该触发迭代操作重新执行。所以在调用track函数时让ITERATE_KEY与副作用函数建立联系。这样响应式数据功能就相对完善了。可以通过下面的代码测试下:
const p = reactive(new Map([
['key1','value1'],
['key2','value2']
]))
effect(()=>{
for(const [key, value] of p){
console.log(key, value)
}
})
p.set("key3","value3")
由于p.entries和p[Symbol.iterator]等价,所以可以使用相同的方法进行拦截
const mutableInstrumentations = {
// 共用iterationMethod方法
[Symbol.iterator]:iterationMethod,
entries: iterationMethod
}
// 抽离为独立的函数,便于复用
function iterationMethod(){
const target = this.raw
const itr = target[Symbol.iterator]()
const wrap = (val) => typeof val === 'object' && val !== null ? reactive(val) : val
// 调用track函数建立响应联系
track(target, ITERATE_KEY)
return {
next(){
const {value, done} = itr.next()
return {
value: value?[wrap(value[0]), wrap(value[1])]:value,
done
}
}
}
}
但是尝试运行使用for…of进行迭代时,会得到错误
// TypeError: p.entries is not a function or its return value is not iterable
for(const [key, value] of p.entries()){
console.log(key, value)
}
错误的原因是p.entries函数返回的对象带有next方法,但是不具有Symbol.iterator方法,因此其不是一个可迭代对象。
这里不要把可迭代协议和迭代器协议搞混。
可迭代协议指的是一个对象实现了Symbol.iterator方法
迭代器协议指的是一个对象实现了next方法
但是一个对象可以同时实现可迭代协议和迭代器协议,如:
const obj = {
// 迭代器协议
next(){
//...
}
// 可迭代协议
[Symbol.iterator](){
return this
}
}
所以只要实现可迭代协议即可
function iterationMethod(){
const target = this.raw
const itr = target[Symbol.iterator]()
const wrap = (val) => typeof val === 'object' && val !== null ? reactive(val) : val
// 调用track函数建立响应联系
track(target, ITERATE_KEY)
return {
next(){
const {value, done} = itr.next()
return {
value: value?[wrap(value[0]), wrap(value[1])]:value,
done
}
},
// 实现可迭代协议
[Symbol.iterator](){
return this
}
}
}