当前位置: 首页 > Web前端 > vue.js

vue2和vue3的数据绑定原理

时间:2023-04-01 11:29:10 vue.js

vue2数据劫持的核心方法:Object.definePropertyH5方法,所以不兼容IE8及以下letobj={},value=1Object.defineProperty(obj,'a',{get(){console.log('这里监听数据采集')returnvalue},set(newValue,value){if(newValue!==value){value=newValueconsole.log('这里监听到数据变化')}}})console.log(obj.a)//这里监听数据的获取1obj.a=2//这里监听数据的变化,所以在重新初始化Vue的时候,数据被劫持了,每个属性都通过Object变成了getter/setter.defineProperty。一旦数据发生变化,就会触发设置,然后更新viewletdata={name:'nike',info:{age:21}}Object.keys(data).forEach(key=>{defineProperty(data,key,data[key])})functiondefineProperty(target,key,value){Object.defineProperty(target,key,{get(){console.log('此处监听数据采集')returnvalue},set(newValue,value){if(newValue!==value){value=newValueconsole.log('此处检测到数据更改')}}})}data.name='tom'//Thedatachangeismonitoredheredata.info.age=22//这里监听数据的获取(这里没有触发变化,get和set是相反的,必须一直触发一个)data.info={age:22}//这里监听数据的获取数据变化至于data.info.age=22,为什么没有触发set?因为上面的逻辑只是劫持了数据下面的一层,无法监听到进一步的变化,所以导致多了两个AthingWatchwatch:{info:{handler(){},deep:true}}这里的deep表示深度监控,这样属性就会被一个一个递归遍历劫持,类似于深度复制vue.$set从字面意思看,手动触发setObject.defineProperty有个bug,就是无法监听数组(因为数组没有键)letdata={name:[],}Object.keys(data).forEach(key=>{defineProperty(data,key,data[key])})functiondefineProperty(target,key,value){Object.defineProperty(target,key,{get(){console.log('这里监听数据获取')returnvalue},set(newValue,value){if(newValue!==value){value=newValueconsole.log('这里监听数据变化')}}})}data.name.push('nike')//这里监听数据获取为了解决这个问题,Vue重写了数组方法//重写pushletoldPush=Array.prototype.pushArray.prototype.push=function(){console.log('thistriggerviewupdate')oldPush.call(this,...arguments)}vue3数据劫持很明显,Object.defineProperty有一些缺陷,不仅要逐一遍历劫持的数据,而且无法监听到array,所以VUE3使用了ES6的ProxyProxy,字面理解就是代理,就像代理一样。一旦绑定了某个明星的数据,那么这个明星就必须要通过代理才能为所欲为。letdata={msg:{a:10},arr:[1,2,3]}lethandler={get(target,key){//惰性监听,获取到的时候只监听对象中的对象,而不是直接递归循环console.log('getkey:'+key)if(typeoftarget[key]==='object'&&target[key]!==null){returnnewProxy(target[key],handler)}returnReflect.get(target,key)},set(target,key,value){letoldValue=target[key]console.log('Updatekey:'+key)if(oldValue!==值){//通知视图更新}returnReflect.set(target,key,value)}}letproxy=newProxy(data,handler)proxy.arr.push(4)为什么每次输出结果都有长度?Proxy的监听数组的实现其实就是把数组变成类数组对象letarr={'0':1,'1':2,length:2}Proxy除了有deleteProperty/apply/getOwnPropertyDescri获取和设置有ptor等12个方法,刚好对应Reflect,所以拦截器可以在这些方法中实现。set(target,key,value){if(key[0]==='_'){thrownewError('这是一个私有变量,不能改变')}returnReflect.set(target,key,value)}双向绑定原理采用订阅者-发布模型,参见https://segmentfault.com/a/11...其中核心组成部分:ListenerObserver:以上数据劫持订阅者容器:当监听器检测到数据变化,遍历订阅者容器并发布消息{this.subs=[]}Dep.prototype={addSub(sub){this.subs.push(sub)},notify(){//每个订阅者都有一个更新方法this.subs.forEach(sub=>sub.update())}}编译核心思想:解析特殊指令,如{{}},@,bind,v-for将dom节点转化为Document分片,提高性能functionCompile(el){this.$el=document.querySelector(el)this.$fragment=this.node2Fragment(this.$el)//将根节点下的所有Dom转化为文档片段this.init()//解析指令this.$el.appendChild(this.$fragment)//将文档片段插入根节点}Compile.prototype={node2Fragment(el){letfragment=document.createDocumentFragment(),childwhile(孩子=el.firstChild){fragment.appendChild(child);}返回片段;}}解析指令比较复杂,略过初始化时触发自己的get}Watch.prototype={update(){//执行Compile方法触发视图更新},get(){Dep.target=this//Dep.target表示当前订阅者letvalue=this.vm[this.exp]//这里会触发Observer的getter,因为数据采集被劫持了Dep.target=null//重置返回值}}Object.defineProperty(data,key,{get(){Dep.target&&dep.addDep(Dep.target)//将当前订阅者添加到订阅者容器中returnval},set(){dep.notify()//如果有变化,通知所有订阅者}})总结Vue双向绑定的原理是采用发布订阅者模式,在初始化的时候劫持数据的各个属性的setter/getter,在dat发布的时候发布消息给订阅者a发生变化,并触发响应监听器回调。每个组件对应一个Watcher实例,在组件渲染过程中会记录触摸到的数据作为依赖。当dependentsetter启动时,会通知Watcher,让组件重新渲染它的framework:MVVMMVC缺点:View和model可以直接通信,相互影响,与之相比:data和view分离,数据驱动view,开发者只需要关心数据,DOM操作被封装