You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
export functionobserve(value: any,asRootData: ?boolean): Observer|void{if(!isObject(value)||valueinstanceofVNode){return}letob: Observer|voidif(hasOwn(value,'__ob__')&&value.__ob__instanceofObserver){
ob =value.__ob__}elseif(shouldObserve&&!isServerRendering()&&(Array.isArray(value)||isPlainObject(value))&&Object.isExtensible(value)&&!value._isVue){ob=newObserver(value)}if(asRootData&&ob){ob.vmCount++}returnob}
export classObserver{value: any;
dep: Dep;
vmCount: number;// number of vms that have this object as root $dataconstructor(value: any){this.value=valuethis.dep=newDep()this.vmCount=0def(value,'__ob__',this)if(Array.isArray(value)){if(hasProto){protoAugment(value,arrayMethods)}else{copyAugment(value,arrayMethods,arrayKeys)}this.observeArray(value)}else{this.walk(value)}}/** * Walk through all properties and convert them into * getter/setters. This method should only be called when * value type is Object. */walk(obj: Object){constkeys=Object.keys(obj)for(leti=0;i<keys.length;i++){defineReactive(obj,keys[i])}}/** * Observe a list of Array items. */observeArray(items: Array<any>){for(leti=0,l=items.length;i<l;i++){observe(items[i])}}}
exportdefaultclassDep{statictarget: ?Watcher;id: number;subs: Array<Watcher>;constructor(){this.id=uid++this.subs=[]}addSub(sub: Watcher){this.subs.push(sub)}removeSub(sub: Watcher){remove(this.subs,sub)}depend(){if(Dep.target){Dep.target.addDep(this)}}notify(){// stabilize the subscriber list firstconstsubs=this.subs.slice()if(process.env.NODE_ENV!=='production'&&!config.async){// subs aren't sorted in scheduler if not running async// we need to sort them now to make sure they fire in correct// ordersubs.sort((a,b)=>a.id-b.id)}for(leti=0,l=subs.length;i<l;i++){subs[i].update()}}}// The current target watcher being evaluated.// This is globally unique because only one watcher// can be evaluated at a time.Dep.target=nullconsttargetStack=[]exportfunctionpushTarget(target: ?Watcher){targetStack.push(target)Dep.target=target}exportfunctionpopTarget(){targetStack.pop()Dep.target=targetStack[targetStack.length-1]}
exportdefaultclassWatcher{vm: Component;expression: string;cb: Function;id: number;deep: boolean;user: boolean;lazy: boolean;sync: boolean;dirty: boolean;active: boolean;deps: Array<Dep>;newDeps: Array<Dep>;depIds: SimpleSet;newDepIds: SimpleSet;before: ?Function;getter: Function;value: any;constructor(vm: Component,expOrFn: string|Function,cb: Function,options?: ?Object,isRenderWatcher?: boolean){this.vm=vmif(isRenderWatcher){vm._watcher=this}vm._watchers.push(this)// optionsif(options){this.deep=!!options.deepthis.user=!!options.userthis.lazy=!!options.lazythis.sync=!!options.syncthis.before=options.before}else{this.deep=this.user=this.lazy=this.sync=false}this.cb=cbthis.id=++uid// uid for batchingthis.active=truethis.dirty=this.lazy// for lazy watchersthis.deps=[]this.newDeps=[]this.depIds=newSet()this.newDepIds=newSet()this.expression=process.env.NODE_ENV!=='production'
? expOrFn.toString()
: ''// parse expression for getterif(typeofexpOrFn==='function'){this.getter=expOrFn}else{this.getter=parsePath(expOrFn)if(!this.getter){this.getter=noopprocess.env.NODE_ENV!=='production'&&warn(`Failed watching path: "${expOrFn}" `+'Watcher only accepts simple dot-delimited paths. '+'For full control, use a function instead.',vm)}}this.value=this.lazy
? undefined
: this.get()}/** * Evaluate the getter, and re-collect dependencies. */get(){pushTarget(this)letvalueconstvm=this.vmtry{value=this.getter.call(vm,vm)}catch(e){if(this.user){handleError(e,vm,`getter for watcher "${this.expression}"`)}else{throwe}}finally{// "touch" every property so they are all tracked as// dependencies for deep watchingif(this.deep){traverse(value)}popTarget()this.cleanupDeps()}returnvalue}/** * Add a dependency to this directive. */addDep(dep: Dep){constid=dep.idif(!this.newDepIds.has(id)){this.newDepIds.add(id)this.newDeps.push(dep)if(!this.depIds.has(id)){dep.addSub(this)}}}/** * Clean up for dependency collection. */cleanupDeps(){leti=this.deps.lengthwhile(i--){constdep=this.deps[i]if(!this.newDepIds.has(dep.id)){dep.removeSub(this)}}lettmp=this.depIdsthis.depIds=this.newDepIdsthis.newDepIds=tmpthis.newDepIds.clear()tmp=this.depsthis.deps=this.newDepsthis.newDeps=tmpthis.newDeps.length=0}/** * Subscriber interface. * Will be called when a dependency changes. */update(){/* istanbul ignore else */if(this.lazy){this.dirty=true}elseif(this.sync){this.run()}else{queueWatcher(this)}}/** * Scheduler job interface. * Will be called by the scheduler. */run(){if(this.active){constvalue=this.get()if(value!==this.value||// Deep watchers and watchers on Object/Arrays should fire even// when the value is the same, because the value may// have mutated.isObject(value)||this.deep){// set new valueconstoldValue=this.valuethis.value=valueif(this.user){constinfo= `callbackforwatcher "${this.expression}"`
invokeWithErrorHandling(this.cb,this.vm,[value,oldValue],this.vm,info)}else{this.cb.call(this.vm,value,oldValue)}}}}/** * Evaluate the value of the watcher. * This only gets called for lazy watchers. */evaluate(){this.value=this.get()this.dirty=false}/** * Depend on all deps collected by this watcher. */depend(){leti=this.deps.lengthwhile(i--){this.deps[i].depend()}}/** * Remove self from all dependencies' subscriber list. */teardown(){if(this.active){// remove self from vm's watcher list// this is a somewhat expensive operation so we skip it// if the vm is being destroyed.if(!this.vm._isBeingDestroyed){remove(this.vm._watchers,this)}leti=this.deps.lengthwhile(i--){this.deps[i].removeSub(this)}this.active=false}}}
constructor(){
........this.value=this.lazy
? undefined
: this.get()}/** * Evaluate the getter, and re-collect dependencies. */get(){pushTarget(this)letvalueconstvm=this.vmtry{value=this.getter.call(vm,vm)}catch(e){if(this.user){handleError(e,vm,`getter for watcher "${this.expression}"`)}else{throwe}}finally{// "touch" every property so they are all tracked as// dependencies for deep watchingif(this.deep){traverse(value)}popTarget()this.cleanupDeps()}returnvalue}
//lifecycle.jsletupdateComponent/* istanbul ignore if */if(process.env.NODE_ENV!=='production'&&config.performance&&mark){updateComponent=()=>{constname=vm._nameconstid=vm._uidconststartTag=`vue-perf-start:${id}`constendTag=`vue-perf-end:${id}`mark(startTag)constvnode=vm._render()mark(endTag)measure(`vue ${name} render`,startTag,endTag)mark(startTag)vm._update(vnode,hydrating)mark(endTag)measure(`vue ${name} patch`,startTag,endTag)}}else{updateComponent=()=>{vm._update(vm._render(),hydrating)}}// we set this to vm._watcher inside the watcher's constructor// since the watcher's initial patch may call $forceUpdate (e.g. inside child// component's mounted hook), which relies on vm._watcher being already definednewWatcher(vm,updateComponent,noop,{before(){if(vm._isMounted&&!vm._isDestroyed){callHook(vm,'beforeUpdate')}}
vue2响应式原理
vue2的依赖追踪的代码基于Object.defineProperty实现,收集触发相关依赖的代码还会依靠Watcher,Dep代码
observer
vue在初始化组件的时候会initData,调用observe(data, true),监听data每层数据,observe函数接受value: any的参数,但是当它不为对象的时候会跳出函数,为什么呢,这个后面会讲。 经过一系列的判断,来到了new Observer(value)
Observer类有三个属性,value,vmCount,dep,两个方法,walk,observeArray,它会给对象绑定一层__ob__为当前的Observer,然后监听每个对象值。在这里,第一次看到了dep字眼,它的主要功能就是收集依赖用的,后面会揭开它的面纱。有人会好奇vmCount是啥,我们知道vue.$set是不能给根对象设置新的值
vm.$set(vm.$data, key, value)
将会抛出错误,所以他是用来判断是不是data的根。在监听值的时候,如果值不为数组就会defineReactive(value),否则将会重新observe一遍每个值,为啥呢,下面讲到defineReactive的时候会解释
上面这段就是最重要的监听对象值变化的函数了。先来到最重要的Object.defineProperty,看到上面会有一段
let childOb = !shallow && observe(val)
这是干嘛的?为什么又跳回去Observe了。Object.defineProperty虽然可以对值进行监听,但是无法监听对象的key的增加,包括对象里对象值的改变
所以我们需要每层都进行监听
遇到数组也需要对每个值进行监听,如果监听数组本身也并不会触发set,所以上面遇到数组才会重新observe一遍。我们可以把数组看成一个对象
对于数组还需要进行其他的hack,使用数组方法的时候也会触发值改变,比如splice,reserve,push等,我们可以监听到Array.prototype某些方法有无被使用到
ok,回到
let childOb = !shallow && observe(val)
,到这里我们知道observe在如果不是对象的时候会跳出,也就是说如果对象还存在子对象会重新在添上一层observe。先看看getter函数
这里面都是关于dep的代码,虽然还没讲dep是什么,但是可以先告诉大家,get主要是做依赖收集,
到set函数,会判断如果值没变,将会直接返回,后面的(newVal !== newVal && value !== value))是啥呢?自己不等于自己?那就只有一种数据类型了
NaN != NaN
。set函数可以接受customSetter函数,在vue里有些值是不能被改变的,比如父辈传过来的值,inject,props,$attrs,$listens监听这些值的时候都可以传自定义setter让用户不能更改值。set最后会改变值并且触发相关依赖更新在defineReactive里会看到一些奇怪难懂的判断
这是为了处理一些用户的边界情况,如果用户事先监听了对象值,会导致触发用户调用的getter,这种行为会很奇怪,所以如果有用户自定义的getter,就不取值
在setter里如果有用户定义的setter就返回也是因为如果不跳出的话,会到下面重新观察值收集依赖,这样会导致和最初的用户行为不一致
具体可以到这个issue进行了解
Dep
Dep的代码比较简单,能看到大部分函数会接受一个参数,Watcher,它的实例会维护一个装了watcher的数组,以便于更新值时触发所有的watcher来更新相关联的值
上面的代码能看到监听值的getter会触发dep的depend方法,但是这个Dep.target又是啥呢?看到最下面,在pushTarget里接收watcher再将它设置到模块化全局的变量里,那啥时候会触发pushTarget函数?
Watcher
Watcher的代码比较长,它可以用于computed,监听值,还有在初始化每个组件都会有updateComponent的wather在值更新的时候重新渲染
能看到一开始实例化对象的时候,会调用get()计算值,在get函数的第一行,
pushTarget(this)
,这会将Dep.target设置当前实例化的对象,然后读取值value = this.getter.call(vm, vm)
,还记得之前对值设置的getter吗?这样就会将依赖收集起来了,所以每次在获取值的时候就会进行依赖收集,不过当然也会防止依赖的重复收集,这基本上就是依赖收集追踪的整个过程了
那数据变更后是怎么更新dom的呢?也没看到有data的watcher啊?
在注入钩子的时候会在vm上定义一个updateComponent的watcher,当模板里需要数据的时候会把依赖添加进来,最后触发
vm._update(vm._render(), hydrating)
,最后patch(newVode,oldVode)
当然watcher还有很多其他方法属性,比如setter触发
dep.notify
时会执行每个watcher.update ,取得最新的值queueWatcher能够保证在一定的时间内不重复更新dom,
lazy是用于computed,它只有在被需要的时候才会计算,并且会缓存值,re-render的时候不会重新计算,当相关值更新时才会计算最新的结果,具体怎么初始化可以到state.js里参考
还有watcher里的user属性,是用于Options api里面的watch
他会在
try{}catch{}
里执行用户定义的callback,防止函数错误导致js停止执行over
The text was updated successfully, but these errors were encountered: