-
Notifications
You must be signed in to change notification settings - Fork 6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
vue3 中的 arrayInstrumentations 数组查找方法解析 #56
Comments
最后的最后要用两次元素查询是为了什么的解释不太对吧。实际上运行测试案例会发现不会走到 const raw = [],
o = {},
ro = reactive({})
// 此时原数组实际上就是个纯纯的数组 + 对象,没有响应式
raw.push(o)
const arr = reactive(raw)
console.log(arr.includes(obj)) // true
console.log(arr.includes(o)) // true 这种情况下会进入 |
Reflect.get(arrayInstrumentations, key, receiver) |
const arrayInstrumentations: Record<string, Function> = {}
;['includes', 'indexOf', 'lastIndexOf'].forEach(key => {
arrayInstrumentations[key] = function(...args: any[]): any {
const arr = toRaw(this) as any
for (let i = 0, l = (this as any).length; i < l; i++) {
track(arr, TrackOpTypes.GET, i + '')
}
// we run the method using the original args first (which may be reactive)
const res = arr[key](...args)
if (res === -1 || res === false) {
// if that didn't work, run it again using raw values.
return arr[key](...args.map(toRaw))
} else {
return res
}
}
}) @caoyifeng007 实际上这里的this和Reflect.get(arrayInstrumentations, key, receiver)没有关系,Reflect.get只是取到这个方法的方法,this是调用的时候确定的,这个方法的调用者是代理的数组对象,所以this指向这个对象。 |
为什么只有这三个方法需要特殊处理,find这种的方法不需要吗 |
问题
在阅读vue3的源码过程中,遇到了很多有意思的代码,比如以下这一段:
这是一组数组方法,当vue3遇到
includes/indexOf/lastIndexOf
这些数组标识方法时,要特殊处理。看到这个方法的时候是有点懵的:
this
是个什么鬼?includes/indexOf
这些方法和Proxy又有什么关系?this
可以看到这里使用了
Reflect.get
执行操作,这个方法的签名为Reflect.get(target, propertyKey[, receiver])
。第三个参数receiver: 如果target对象中指定了getter,receiver则为getter调用时的this值,所以上文出现的this其实是这里的target对象,也就是那个reactive的proxy对象。
const arr = toRaw(this) as any
即获取了当前响应式对象指向的那个原始数组。includes/indexOf这些方法和Proxy有什么关系
实际上,并不是
arr[0]
这样直接获取元素的时候才会触发proxy的getter,任何对数组元素、方法的访问都会。我们对proxy的数组做
push includes
操作,可以看到控制台输出了很多信息。当你使用这三个标志方法去查找元素时,就遇到了一个问题:这个数组里是否存在reactive对象?他们是不是和raw值混在一起?
看这个例子:
arr是一个reactive的数组,当把一个空对象push进去时,arr中保存的已经不是那个原始的raw对象了,这里面存入的是一个响应式的object。
当
arr.includes(raw)
时,如果不做上述特殊处理,返回结果应该是false,这显然有点不符合使用者的思维逻辑。所以,当遇到数组标志查询方法时,需要转换成原始值再进行查找。
遇到这些方法为何要自行遍历原始元素,再去track所有的元素呢?
百思不得其解,去翻了当时的commit和解决issue,大概弄懂了。
composition API设计之初,reactive的数组元素如果是一个ref类型,是会自动展开的,不需要
arr[0].value
去操作,这样带来了一系列的问题:sort、reverse
会出问题,ref类型并不会移位,但是值却变了;Map Set
,本来就不会自动展开,那数组也不该去自动展开ref类型的元素基于以上原因,vue从此移除掉了数组元素的ref类型展开。
当这里的数组元素使用raw值进行查找,不再操作proxy对象,就需要手动遍历进行track,实现依赖收集了。
这里确实要吐槽一句,hack的东西越多,心智负担就越重,不如保持simple clear,也许使用上会繁琐一些,但是权衡之下,应该尽可能的保持简洁。
vuejs/core#737 这里有关于这个问题的讨论,有兴趣的话值得一看。
最后要用两次元素查询是为了什么
直接看相关的测试用例:
当raw数组包含一个reactive对象obj时,再把raw数组转换成reactive数组,这时是否包含obj?
这里就不能直接比较toRaw后的原始对象了,这种情况就要查找toRaw之前的数组元素才能找到。
但是除此之外,大部分的查找还是需要使用raw对象的,所以这里要做两次查询满足所有场景需求,性能嘛,当然就差一些了。
总结
看源码多看commit信息+测试用例+issue讨论,事半功倍。
The text was updated successfully, but these errors were encountered: