vue的响应式数据原理详解

vue2的响应式数据的原理

在vue实例上添加 _data,指向data,然后通过_ data得到一个深度劫持的对象,然后再把组件实例需要的方法和 _ data添加到这个深度劫持的对象中,这样就得到了一个组件实例,当我们读写组件实例里的这些属性,就会触发object.defineproperty()gettersetter,通过gettersetter来读写 _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中,经过打印和使用我们可以发现,对于数组的劫持使用的并不是 definePropertypush、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.$setthis.$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响应式数据原理的简单总结。最后感谢大家的观看,希望这篇文章能给你带来帮助。如果有小伙伴有一些疑问或建议,欢迎提出和分享。

你可能感兴趣的:(vue,javascript)