【vue设计与实现】非原始值的响应式方案 14-代理Set和Map-迭代器方法

接下来,谈论下集合类型的迭代器方法。集合类型有三个迭代器方法:
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
		}
	}
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值