<>vue2的响应式数据的原理
在vue实例上添加 _data,指向data,然后通过_ data得到一个深度劫持的对象,然后再把组件实例需要的方法和 _ data
添加到这个深度劫持的对象中,这样就得到了一个组件实例,当我们读写组件实例里的这些属性,就会触发object.defineproperty()的getter和
setter,通过getter和setter来读写 _data的属性,下面是简单的代码实现。
function deepdefineobj(_data,isaddconfig=false,config={}){ let vc // 组件实例
isaddconfig?vc={...config,_data} : vc=Array.isArray(_data)?[]:{} // 深度劫持_data
for(let k of Object.keys(_data)){ Object.defineProperty(vc,k,{ get(){console.log
('read',_data[k]) return _data[k]}, set(v){console.log('write',v) _data[k]=v} })
``const isobj=Array.isArray(_data[k]) || Object.prototype.toString.call(_data[k]
)==='[object Object]' if(isobj) _data[k]=deepdefineobj(_data[k]) } return vc }
const _data=data={ // data选项的数据源 name:"xm", age:18, son:['a','b'], wife:{name:
'hh',age:18,sister:['e','f']} } const vc=deepdefineobj(_data,true,{$emit:'emit
event'}) // 创建组件实例 console.log(vc) console.log(data) console.log('----------')
vc.age=20 // 触发响应式 console.log('----------') vc.son[0] // 触发响应式 vc.wife.sister[0
] // 触发响应式 console.log('----------') vc.son.push('c')
//这里并没有触发son属性的写,虽然是改变原数组的方法,但数组地址没变
在vue2中改变原数组的方法,如:push、unshift具备响应式,无疑是vue做了特殊处理。在vue中,经过打印和使用我们可以发现,
对于数组的劫持使用的并不是 defineProperty,push、unshift等方法具备响应式是因为vue对数组原型上的这些方法进行了重写
<>vue2响应式数据的限制分析
初始化时,vue根据data对象在组件实例上做了深度劫持,根据 Object.defineProperty()
,当我们做读写操作时,vue可以捕获到,但如果是进行增删操作,vue无法捕获,这时数据就不具备响应式。
下面看一个例子:
<p>{{user}}</p> <p>{{arr[0]}}</p> <button @click="changeitem">
通过索引值改变数组项以及在内存里向data添加数据,看看是否具备响应式</button> <script> data:{ user:{name:"xm",age
:18}, arr:[1,2,3,4,5,6] }, methods:{ changeitem(){ this.arr[0]=8 this.user.id=
"1" } } </script>
点击按钮后,我们会发现页面两处都没有更新
解决方案:
对于数组:vue对数组的某些方法进行了处理,使得改变原数组的方法具备响应式,如:push、unshift
其他情况,可以使用this.$set和this.$delete方法,this.$set(target,key,value)
第一个参数是需要劫持增删的目标对象,第二个参数是要添加的属性名,第三个参数是属性名对应的属性值,使用后,target[key]也是具备响应式的
<>vue3完美的响应式数据
在vue3里解决了vue2响应式数据限制的 所有 问题,无论是对象还是数组不再需要使用$set和$delete
,也不再需要依赖于重写的数组方法来达到数组的响应式了。下面是简单代码实现
function deepproxy(_data){ for(let [k,v] of Object.entries(_data)){ const isobj
=Array.isArray(v) || Object.prototype.toString.call(v)==='[object Object]' if(
isobj) _data[k]=deepproxy(v) } const data=new Proxy(_data,{ get (_data,k,proxy){
console.log('read',_data[k]);return Reflect.get(_data,k)}, set (_data,k,v,proxy)
{console.log('write||add',v);Reflect.set(_data,k,v)}, deleteProperty(_data,k,
proxy){console.log('delete',k);return Reflect.deleteProperty(_data,k)} }) return
data} const _data={ // 组件数据源 name:"xm", age:18, son:['a','b'], wife:{name:'hh',
age:18,sister:['e','f']} } const data=deepproxy(_data) // 创建响应式数据 console.log(
data) console.log('---------') data.son // 触发响应式 data.wife.sister // 触发响应式
console.log('---------') data.wife.sister[2]='h' // 触发响应式 console.log(
'---------') data.toString() // 触发响应式 //read function toString()
,这里可以看到,proxy可以劫持到原型上属性的增删改查 //console.log(reactive(_data)) // Proxy {name:
'xm', age: 18} //使用 Reflect 操作对象的属性,是因为该操作如果出错不会阻塞程序运行,而直接操作对象属性的方式出错会阻塞程序
<>结尾
这是我对vue响应式数据原理的简单总结。最后感谢大家的观看,希望这篇文章能给你带来帮助。如果有小伙伴有一些疑问或建议,欢迎提出和分享。