vue3为什么要用proxy替代defineProperty
在这之前,我们得先了解下vue的核心理念mutable
不管是vue2还是vue3,在实现的过程中,核心概念一直保持稳定,以可变数据源为核心的理念,来实现整个UI变动更新
用最简单的讲法就是:初始化数据生成了页面,直接修改源数据触发更新,页面重新渲染
关注vue的人都知道,vue3里面使用了proxy替换了defineProperty,
在使用vue2的时候,我们经常会碰到一个问题,添加新的对象属性obj.a = 1会无法被vue2劫持,必须使用vue2提供的$set方法来进行更新
这个的原因想必大家也都清楚,因为defineProperty只能对当前对象的其中一个属性进行劫持
const a = { b: 1, }; Object.defineProperty(a, 'b', { set: function() {}, get: function() {}, });
当我们给a对象新增一个属性的时候,当前新增的属性并没有被defineProperty劫持,虽然在对应的对象上依旧成功的生成了一个新的属性,但是我们知道,vue2是通过defineProperty的setter与getter进行数据劫持的,既然新增的数据并没有被劫持,所以无论怎么更新,页面依旧不会重新渲染
而在vue3中,使用proxy来进行数据代理就完全没有这个顾虑了
const p = new Proxy({ a: 1, b: 2, }, { get: function(obj, value) { console.log('get', obj, value); return Reflect.get(obj, value); }, set: function(obj, prop, value) { console.log('set', obj, prop, value); return Reflect.set(obj, prop, value); }, })
proxy对于数据的代理,是能够响应新增的属性,当新增一个属性的时候,可以响应到get中,对当前对象进行代理
vue3是如何通过proxy代理的
首先可以看下vue3新增的几个主要apiref, reactive, effect,computed
ref和reactive const normal = ref(0); const state = reactive({ a: 1, b: 2, })
vue3中对vue2的兼容处理也是使用了reactive,即instance.data = reactive(data),将整个data属性使用reactive进行代理
我们知道,vue2中的data就是使用Object.definePerproty进行数据劫持的, 那么在reactive中,他是如何使用proxy进行数据代理的,来兼容老的书写方式与新的compositionApi
ps: 由于在reactive里面也只是通过proxy对传入的数据校验和代理,最主要的还是set和get,所以我们还是直接上垒吧,毕竟心急吃得了热豆腐
get
可以分析一下,vue2也好,vue3也罢,针对于数据的获取所做的事情主要内容不会有什么区别
- 获取当前需要的key的数据
- 依赖采集
但是,针对于vue3使用proxy的特性,在这边额外做了一部分的兼容
- 如果获取的数据是一个对象,则会对对象再使用reactive进行一次数据的代理
- 如果是shallow类型的数据代理, 则直接返回当前获取到的数据
effect依赖采集
vue除去正常的data的数据代理以外,还有对应的computed和watch,而在vue3中直接使用了watchEffect和computed方法能够直接生成对应的内容
他们的数据更新和依赖处理都是依托于当前data数据上的get进行依赖的收集
掐头去尾的来看最核心的代码
// targetMap当前所有代理的数据的一个Map集合 // depsMap当前代理的数据的每一个Key所对应的Map集合 // dep当前代理的数据中的key的对应依赖 // activeEffect当前由effect或者computed生成的数据 let depsMap = targetMap.get(target) if (!depsMap) { targetMap.set(target, (depsMap = new Map())) } let dep = depsMap.get(key) if (!dep) { depsMap.set(key, (dep = new Set())) } if (!dep.has(activeEffect)) { dep.add(activeEffect) activeEffect.deps.push(dep) }
单纯从这段代码出发去解读的话,可能会有一定的困难,换个角度,从vue3的整体使用情况出发,返回来解读这段代码
setup() { const b = reactive({ c: 1, b: 2, }); // effect是vue中的reactivity包直接返回出来的方法 const a = effect(() => { return b.c; }) }
首先,在effect中使用了前面通过reactive定义的b,从表面现象出发的话,我们能知道,当b.c发生变化的时候,a也会同步发生变化
这个变化的原因就是上述源码中的activeEffect,当创建的effect被调用的时候,会将activeEffect设置为自身,并执行相应的回调函数,函数的调用会触发到各自使用到的数据的getter,将对应的effect依赖注入到每个使用的数据上
至于为什么会设置这么复杂的一个属性的依赖获取,是因为使用proxy的原因,proxy代理了一整个对象,就不能像vue2使用Obect.defineProperty直接在getter里面就当前的字段进行一个依赖绑定,所以在vue3中是直接将整个对象作为一个Map,每个Map的key都是对应的属性,而value则是所有依赖当前属性的对象
set
同get,依旧保持着原先的思路跟模式
- 设置当前数据
- 发布已订阅的数据(触发依赖更新)
在vue3中,还是有部分区别的,毕竟是单独拉出去的一个库
- 如果直接调用effect,当检测的数据发生变化的时候会直接修改
- 如果调用watch或者watchEffect,则会走vue自身的调度方案
所以,如果想当前的数据直接可以更新的话, 可以优先使用effect,他会比watchEffect的更新速度快一点,劣势是可能很多东西得自己写 =,=
至于怎么实现的其实就很简单了
- 获取当前更新的数据的受依赖项
- 分组进入等待运行的Set中
- 执行
但是里面有一个特殊的处理,针对于数组的length属性,这个属性是有一定区别的,接下来具体讲讲在vue3中的数组操作
数组
在vue2中的,针对数组是多做了一层处理,代理了数组的基本方法,这是因为使用Object.defineProperty在数组上面天然存在劣势
具体原因在vue的文档中写的非常清楚了,这里就不详细叙述了
而在vue3中使用proxy就完美的解决了这个问题,只是因为proxy能够监听数组的变化,做个测试
const a = new Proxy([1,2], { get: function(obj, prop) { console.log('get', obj, prop); return Reflect.get(obj, prop); }, set: function(obj, prop, value) { console.log('set', obj, prop, value); return Reflect.set(obj, prop, value); }, }); a.push(1); get [1,2] push get [1,2] length set [1,2] 2 1 set [1,2, 1] length 3
当我们代理了一个数组之后,直接调用push插入一个新的数据,能够明显的看到getter跟setter都会被调用两次,一次是调用的push方法,而另一次是数组的长度length,也就是说,proxy不仅仅会检测到我们当前调用的方法,还能够知道我们的数据长度是否发生了变化
看到这边,可能会有一个疑惑,push是对当前数组进行的操作,但是数组里面还有部分方法是会返回一个新的数组,proxy是否会对新生成的数组也进行代理,这里我们拿splice举个例子
// a= [1,2] a.splice(0, 1) get [1,2] push get [1,2] length get [1,2] constructor get [1,2] 0 get [1,2] 1 set [1,2] 0 2 set [2,empty] length 1
从表现形式来看,proxy代理之后的数组只会对当前数组的内容进行监听,也就是调用splice之后新生成的数组的变化是不会被代理的
现在我们回过头来看下vue3的trigger方法,这个是vue在set完成之后触发的依赖更新,同样的掐个头去个尾,除去正常的执行以外,我们看下针对数组做的优化
// add方法是将当前的依赖项添加进一个等待更新的数组中 else if (key === 'length' && isArray(target)) { depsMap.forEach((dep, key) => { if (key === 'length' || key >= (newValue as number)) { add(dep) } }) }
由于我们知道, 在一次操作数组的时候会进行多次的set,那么如果每次set都要去更新依赖的话,会造成性能上的浪费,所以在vue3里面只有在set length的时候才会去调用add方法,然后统一执行所有的更新
结语
不得不说,proxy比defineProperty强大了太多,不仅解决了vue的历史难题,让vue的体验更上了一层,更是去除了不少因为defineProperty而必须要的方法,精简了vue的包大小
虽然proxy的兼容性是比defineProperty差不少,但是在vue里面基本已经抛弃了IE,所以如果你的项目需要在ie下运行的话,那就请放弃vue这个选择,使用低版本的react的吧,哈哈哈哈哈哈
在移动端里面基本上就是没有这种版本的限制,实在是版本低不能使用proxy的话,相信去找找polyfill是能够找到的
上一篇:Vue 递归多级菜单的实例代码
栏 目:vue
本文标题:vue3为什么要用proxy替代defineProperty
本文地址:http://www.codeinn.net/misctech/74838.html