Vue3在伟大祖国70周岁时终于和大家见面了。🚩
在学习Vue3的数据绑定前,先预习一下相关知识:
1、Proxy,我之前写过一篇Proxy介绍,可以参考下。
2、WeakMap
然后我们就能愉快的玩耍了。
Vue3中关于数据绑定这块的代码主要在这里
我们主要关注reactive.ts和baseHandlers.ts这两个文件里的内容。
1.Proxy已知的两个问题
Proxy本身不支持对象内部的深度侦测,需要自己实现
Proxy本身支持数组变化侦测,但会有多次触发的风险
2.Vue3是如何解决
2-1
解决第一个问题,对于初学者,我们可以采用暴力递归的办法,一开始就把对象内部的所有对象都用Proxy代理一遍。
这样操作的结果就是,proxy变成下图所示的结果:
可以看到,对象内部的对象和数组都已经被代理了。当obj是一个非常大且复杂的对象时,这样做性能肯定不好,我们来看看Vue是怎么做的。
Vue中,一开始我们只是把最外层的对象做Proxy操作,而内层的对象,只有在需要访问的时候,才会惰性地创建Proxy代理。为此,我们需要对handler
函数做一下改造。
完成上述操作后,Vue3还做了一件事,就是把动态创建的Proxy都存在一个WeakMap
中,这样我们就不用每次执行get
操作的时候都会去动态创建了,毕竟这是很耗费内存的。在Vue3源码中我们可以有两个WeakMap
,就是干这事的。为此,我们还需要改造一下createReactiveObject
方法,传入两个WeakMap
来做缓存。另外,toRaw
这个WeakMap
干嘛用的,留给大家思考一下😊。
2-2
下面我们来解决第二个问题,假设我们有一个数组arr=[1,2]
,当我们执行arr.push(3)
时,arr
有两个属性发生了变化,一个是新增了一个key=2
的索引属性,对应value就是我们刚添加的3,这时可以把数组理解成一个对象;此外arr
的length
属性也发生了变化,增加了1。所以,如果用Proxy劫持了数组的set方法时,此时会触发两次set操作,一次对应的key是2
,一次对应的key是length
。
每次执行set
操作新增或者修改某个属性时,我们先利用Object.hasOwnProperty
判断这个属性是否存在,如果不存在则直接
trigger add ...
;如果是已有属性,则判断当前value
和target
的上相同key
对应的value
,也就是代码中的oldValue
是否相等,如果不相等,才会去执行trigger set ...
。这里需要细细理解一下,当触发key
为length
的回调时,由于此时的target
,就是被代理的数组对象,在上一次key
为2
的回调触发时,执行了Reflect.set(target, key, value, receiver)
,此时它的数组长度已经+1
了,所以在key
为length
的回调中,此时oldValue
和value
是相等的,都是3
。这样,我们就避免了多次触发render
的操作,Vue3中的这段代码还是写的很精妙的,🐂🍺。
到此,Proxy原生的两个大问题都被合理解决了,接下来就是一些常规的前置条件判断,Vue3中只对数组、对象、Map、WeakMap、Set、WeakSet这几类数据做了数据绑定,对基本数据类型和一些JS内置对象如Date、Promise和Reg等是直接返回不会做Proxy处理的。
下面我们看一眼本文中用到的demo和Vue3源码的对比,可以看到,大体的结构是一样的,当然demo中做了很多的简化操作。
3.demo小结
看到这里,你是不是对Vue3中的数据绑定有了一个大概的了解。完整demo代码在这里。写的不完善的地方,还请各位大佬多多指教。