宝哥软件园

vue响应系统中观察者、观察者和dep的源代码分析

编辑:宝哥软件园 来源:互联网 时间:2021-08-26

Vue的响应系统

Vue最独特的特点之一是其无创反应系统。数据模型只是普通的JavaScript对象,修改时视图会更新,使得状态管理非常简单直接。我们可以只关注数据本身,而不是手动处理数据到视图的渲染,避免了繁琐的DOM操作,提高了开发效率。

Vue的响应系统依赖于三个重要的类:Dep类、Watcher类和observer类,然后利用发布-订阅模式的思想将它们混合在一起(如果你不知道发布-订阅模式,可以看看我之前的文章发布-订阅模式和Observer模式)。

观察者

Observe扮演发布者的角色,他的主要作用是调用defineReactive函数,其中Object.defineProperty方法用来劫持/监控对象的每个子属性的数据。

部分代码表示

定义活动函数,Observe的核心,劫持数据,在setter中给Dep(调度中心)添加观察员,在getter中通知观察员更新。

函数定义reactive (obj,key,val,自定义setter,浅层){//监听属性key //key point:在闭包中声明一个Dep实例保存观察器实例var Dep=new Dep();var getter=propertyvar setter=propertyif(!getter arguments . length===2){ val=obj[key];}//执行observe,监听属性键表示的value val的子属性var childOb=observe(val);Object.defineproperty (obj,key,{enumerable: true,可配置: true,get : function reactive getter(){//get value var value=getter?getter . call(obj): val;//依赖集合:如果有活动的Dep.target(观察者-观察者实例)if(Dep.target) {//将Dep放入当前观察者的Dep,同时将观察者放入dep,等待变更通知Dep . depend();如果(childOb) {//收集子属性的依赖关系//事实上,同一个观察器观察者实例被放入两个dep//一个是它自己闭包中的dep,另一个是子属性的dep child ob . dep . dep dep dep();}}返回值},设置:函数reactive setter(new val){//get value var value=getter?getter . call(obj): val;if(newVal===value || (newVal!==newVal值!==value)){ return } if(setter){ setter . call(obj,newVal);} else { val=newVal}//需要再次观察新值,以确保数据响应childOb=observe(newVal);//要点:遍历dep.subs并通知所有观察者dep . notify();} });}Dep

Dep扮演着调度中心/用户的角色,它的主要作用是收集观察者并通知观察者目标的更新。每个属性都有自己的消息订阅者dep,用于存储订阅该属性的所有观察者对象。当数据改变时,它将遍历观察者列表(dep.subs),通知所有的观察者,并让订阅者执行他们自己的更新逻辑。

部分代码表示

Dep的设计相对简单,即收集依赖关系和通知观察者

//Dep构造函数vardep=functiondep () {this。id=uidthis . subs=[];};//将观察者dep . prototype . addsub=function addsub(sub){ this . subs . push(sub)添加到dep的观察者列表subs中;};//移除观察者dep . prototype . remove sub=function remove sub(sub){ remove(this。副秘书长观察员名单中的副秘书长;};Dep . prototype . depend=function depend(){//Dependency collection:如果当前有观察者,则将dep放入当前观察者的deps中。//同时将当前观察者放入观察者列表subs if(dep . target){ dep . target . adddep(this);}};循环处理dep . prototype . notify=function notify(){///,并运行每个观察器的更新接口var subs=this . subs . slice();for(var i=0,l=subs.lengthI l;i ) { subs[i]。update();}};//Dep.target是一个观察器,它是全局唯一的,因为任何时候只处理一个观察器。Dep.target=null//等待的观察者队列var target stack=[];函数pushTarget(_target) {//如果当前有一个正在处理的观察器,则在if(dep . Target){ target stack . Push(dep . Target)的情况下,将他推入要处理的队列中;}//将Dep.target指向要处理的观察者Dep.target=_ target}函数popTarget() {//将Dep.target指向堆栈顶部的观察者,并将其从队列中移除Dep . target=TargetStack . pop();}观察者

观察者扮演订阅者/观察者的角色。他的主要作用是为观察到的属性提供回调函数并收集依赖关系(例如,在计算计算出的属性时,vue会将属性所依赖的数据的dep添加到它自己的dep中)。当观测值发生变化时,它会收到dep的通知,从而触发回调函数。

部分代码表示

Watcher类的实现很复杂,因为它的实例分为三种类型:渲染观察器(渲染观察器)、计算属性观察器(计算观察器)和监听观察器(普通观察器)。这三个实例分别构建在三个函数中:mountComponent、initComputed和Vue.prototype.$watch。

Normal-watcher:我们在component hook函数watch中定义的就属于这种类型,即只要被监控的属性发生变化,定义的回调函数就会被触发,这个watch的表达式就是我们编写的回调函数的字符串形式。

计算观察者:我们在组件钩子函数中定义的计算属于这种类型。每个计算出的属性最终都会生成一个相应的观察器对象。但是这种守望者有一个特点:当计算出来的属性依赖于其他数据时,不会立即重新计算属性,只有在以后需要从其他地方读取属性时,才会真正计算出来,即具有懒计算的特点。此监视的表达式是计算属性中的属性名称。

渲染观察器:每个组件都有一个渲染观察器,当数据/计算属性发生变化时,将调用该观察器来更新组件的视图。这个手表的表达式是function () {VM。_更新(虚拟机。_ render(),补水);}。

除了功能上的不同,这三个观察器还有一个固定的执行顺序,即:计算-渲染-正常-观察器-渲染-观察器。

这种安排是有原因的,以便在更新组件视图时尽可能确保计算属性是最新的值,如果呈现观察器在计算呈现之前,则在更新页面时,计算值将是旧数据。

这里我们只看部分代码

函数Watcher(vm,expOrFn,cb,options,isrenderWatcher){ this。VM=VMif(isRenderWatcher) { vm ._观察者=这个;} vm ._watchers.push(这个);//选项if(options){ this。deep=!options.deep//是否启用深度监听this.user=!options.user//主要用于错误处理,侦听器看守人的用户为没错,其他基本为false this.lazy=!选项。懒惰;//惰性求职,当属于计算属性看守人时为true this.sync=!options.sync//标记为同步计算,三大类型暂无} else { this。深度=这个。用户=这个。懒惰=这个。sync=false} //初始化各种属性和选项/观察者的回调//除了侦听器看守人外,其他大多为空函数this.cb=cbthis。id=uid $ 1;//用于批处理这是主动的。的=truethis.dirty=this。懒惰;//对于懒惰的观察者来说,这个。deps=[];这个。NewDeps=[];这个。desi des=new _ Set();这个。new depds=new _ Set();这个。表达式=exporfn。ToString();//解析expOrFn,赋值给this.getter //当是渲染看守人时,expOrFn是updateComponent,即重新渲染执行渲染(_更新)//当是计算看守人时,expOrFn是计算属性的计算方法//当是侦听器看守人时,expOrFn是看属性的名字,这个。可换股债券就是看的处理者属性//对于渲染看守人和计算看守人来说,expOrFn的值是一个函数,可以直接设置getter //对于侦听器看守人来说,expOrFn是看属性的名字,会使用解析路径函数解析路径,获取组件上该属性的值(运行getter) //依赖(订阅目标)更新,执行更新,会进行取值操作,运行watcher.getter,也就是expOrFn函数if(EXPorfn的类型==' function '){ this。getter=EXPorfn} else { this。getter=parsePath(expOrFn);} this.value=this.lazy?未定义的:这个。get();};//取值操作观察者。原型。get=函数get(){//Dep。目标设置为该观察者pushTarget(这个);var VM=this.vm//取值定义变量值=this.getter.call(vm,VM);//移除该观察者popTArGet();返回值};观察者。原型。addDeP=函数addDeP(DeP){ var id=DeP。id;if(!这个。新部门。has(id)){//为观察者的依赖的列表添加依赖把这个放下。新部门。add(id);这个。纽德普斯。push(dep);if(!这个。DEPDs。has(id)){//为资料执行防止添加该观察者dep.addSub(这个);} }};//当一个依赖改变的时候,通知它updateWatcher。原型。update=函数update(){//三种观察者,只有计算属性看守人的懒惰的设置了没错,表示启用惰性求值如果(这个。懒){这个。dirty=true} else if(this.sync) { //标记为同步计算的直接运行快跑,三大类型暂无,所以基本会走下面的queueWatcher对此进行了分析。run();} else { //将看守人推入观察者队列中,下一个滴答声时调用。 //也就是数据变化不是立即就去更新的,而是异步批量去更新的queueWatcher(这个);}};//更新执行后,运行回调cbWatcher.prototype.run=函数run(){ if(this。有效){ var值=这个。get();如果(值!==这个。value | | isObject(value)| | this。deep){ var old value=this。价值;this.value=value//运行可换股债券函数,这个函数就是之前传入的看中的处理者回调函数如果(这个。用户){尝试{这。CB。叫(这个。虚拟机、价值、旧价值);} catch(e) { handleError(e,this.vm,(' watcher的回调 ' '(这个。表达式)' ' '));} } else { this.cb.call(this.vm,值,旧值);} } }};//对于计算属性,当取值计算属性时,发现计算属性的看守人的肮脏的是true//说明数据不是最新的了,需要重新计算,这里就是重新计算计算属性的值观察者。原型。evaluate=function evaluate(){ this。值=这个。get();this . dirty=false };//收集依赖观察者。原型。depend=function depend(){ var this $ 1=this;var I=这个。德普斯。长度;while(I-){ 0这个1.deps[i]美元.depend();}};总结

Observe是监视数据,Dep是一个订阅者,每个被监视的数据都有一个Dep实例,N个订阅者(观察者)对象观察者都存储在Dep实例中。

当被监控的数据被获取时,如果有Dep.target(一个观察者),则意味着观察者依赖于数据(例如,在计算一个属性时,如果使用其他被监控的数据来计算一个属性,则意味着该属性依赖于其他属性,并且其他属性将被赋值),并且该观察者将被添加到数据的订阅者subs中。当以后数据发生变化时,会通知(观察者是否已经存在于订阅者中,会根据观察者id来判断),观察者也会将数据的订阅者dep添加到自己的dep中,方便其他地方使用。

当被监控的数据被赋值(setter)时,dep.notify()将被触发,数据订阅者中的观察者将被循环更新操作。

以上就是本文的全部内容。希望对大家的学习有帮助,支持我们。

更多资讯
游戏推荐
更多+