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

Vue响应式系统(变化检测)

时间:2023-04-01 00:48:44 vue.js

变化检测分为两种,一种是“推”,一种是“拉”。Angular和React的变化检测是“拉”的,也就是说,当状态发生变化时,它不知道哪个状态发生了变化,只知道状态可能发生变化,然后发送一个信号告诉框架,framework收到信号后,会进行暴力比较,找出哪个DOM需要重新渲染。这也是Angular的dirtycheck(基于zoom.js,由$digest函数触发)的过程,而react使用的是虚拟DOM。Vue的变化检测是“推”的。当状态发生变化时,Vue可以立即知道哪些状态发生了一定程度的变化,并进行更细粒度的更新;也因为粒度越细,每个状态绑定的依赖越多,内存中依赖跟踪的开销越小。越大,因此,Vue也引入了虚拟DOM的概念,为组件绑定一个细粒度的状态(Vue的另一个核心:单文件组件化),这样当状态发生变化时,会通知组件,虚拟组件内部使用DOM进行比较和更新。众所周知,Vue2.x版本使用Object.defineProperty,Vue3.x使用ES6Proxy进行变更检测。下面主要讲Object和Array的变化检测:(注:以下代码块均来自Vue2.6.10源码)一、Object的变化检测1、Object通过Object.defineProperty将属性转换为getter/setter形式,以跟踪更改,在getter中收集依赖项并在setter中触发依赖项。第一版Object.defineProperty:functiondefineReactive$$1(obj,key,val){Object.defineProperty(obj,key,{enumerable:true,configurable:true,get:functionreactiveGetter(){returnval},set:functionreactiveSetter(newVal){if(val===newVal){return}val=newVal;}});}2.在getter中收集依赖,依赖存储在Dep中,添加、删除、更新等。Dep的封装:varuid=0;varDep=functionDep(){this.id=uid++;这个.subs=[];};Dep.prototype.addSub=functionaddSub(sub){this.subs.push(sub);};Dep.prototype.removeSub=functionremoveSub(sub){remove(this.subs,sub);};Dep.prototype.depend=functiondepend(){if(Dep.target){Dep.target.addDep(this);}};Dep.prototype.notify=functionnotify(){//首先稳定订阅者列表varsubs=this.subs.slice();对于(vari=0,l=subs.length;i-1){returnarr.splice(index,1)}}}第二个版本Object.defineProperty:functiondefineReactive$$1(obj,key,val){vardep=newDep();//新建对象.defineProperty(obj,key,{enumerable:true,configurable:true,get:functionreactiveGetter(){if(Dep.target){//Adddep.depend();//Add}returnval},set:functionreactiveSetter(newVal){if(newVal===val){return}val=newVal;dep.notify();//新添加}});}3。所谓依赖就是wather,只有watcher触发的getter才会将依赖收集到Dep中,当数据发生变化时,会循环依赖列表,并通知所有watcher一次Watcher原则:先将自己设置为global唯一指定位置(pushTarget(this)),然后读取数据(value=this.getter.call(vm,vm);)触发数据的getter。然后在getter中,会从全局唯一的位置读取正在读取数据的watcher,并将watcher收集到Dep中。/***观察者解析表达式,收集依赖项,*并在表达式值更改时触发回调。*这用于$watch()api和指令。*/varWatcher=functionWatcher(vm,expOrFn,cb){this.vm=vm;这个.cb=cb;if(typeofexpOrFn==='function'){this.getter=expOrFn;}else{this.getter=parsePath(expOrFn);//parsePath读取一个字符串keypath}};/***评估getter,并重新收集依赖项。*/Watcher.prototype.get=functionget(){pushTarget(this);//这是当前的观察者实例。查看上面的pushTarget并将watcher实例分配给Dep。目标变量值;varvm=this.vm;try{value=this.getter.call(vm,vm);//读取值时,触发getter主动将this添加到Dep(依赖集合)}catch(e){throwe}finally{}returnvalue};/***订阅者接口。*将在依赖项更改时调用。*/Watcher.prototype.update=functionupdate(){/*istanbulignoreelse*/if(this.lazy){this.dirty=true;}elseif(this.sync){this.run();}else{queueWatcher(this);}};4.递归创建一个Observer方法,将对象中的所有对象数据(包括子数据)转化为反应形式。只有Object类型会调用walk将每个属性转换成getter/setter形式进行检测,并在defineReactive$$1中添加observe(newObserver(value);)递归子属性。varObserver=functionObserver(value){this.value=value;如果(Array.isArray(value)){}else{this.walk(value);}};Observer.prototype.walk=functionwalk(obj){varkeys=Object.keys(obj);对于(vari=0;i