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
constcomputedWatcherOptions={lazy: true}functioninitComputed(vm: Component,computed: Object){constwatchers=vm._computedWatchers=Object.create(null)constisSSR=isServerRendering()for(constkeyincomputed){constuserDef=computed[key]constgetter=typeofuserDef==='function' ? userDef : userDef.getif(process.env.NODE_ENV!=='production'&&getter==null){warn(`Getter is missing for computed property "${key}".`,vm)}if(!isSSR){// 新建一个计算watcherwatchers[key]=newWatcher(vm,getter||noop,noop,computedWatcherOptions)}// 组件的computed会在新建组件构造器是挂载到原型对象上if(!(keyinvm)){defineComputed(vm,key,userDef)}elseif(process.env.NODE_ENV!=='production'){if(keyinvm.$data){warn(`The computed property "${key}" is already defined in data.`,vm)}elseif(vm.$options.props&&keyinvm.$options.props){warn(`The computed property "${key}" is already defined as a prop.`,vm)}}}}
// 用Object.defineProperty定义计算属性exportfunctiondefineComputed(target: any,key: string,userDef: Object|Function){constshouldCache=!isServerRendering()if(typeofuserDef==='function'){sharedPropertyDefinition.get=shouldCache
? createComputedGetter(key)
: createGetterInvoker(userDef)sharedPropertyDefinition.set=noop}else{sharedPropertyDefinition.get=userDef.get
? shouldCache&&userDef.cache!==false
? createComputedGetter(key)
: createGetterInvoker(userDef.get)
: noopsharedPropertyDefinition.set=userDef.set||noop}if(process.env.NODE_ENV!=='production'&&sharedPropertyDefinition.set===noop){sharedPropertyDefinition.set=function(){warn(`Computed property "${key}" was assigned to but it has no setter.`,this)}}Object.defineProperty(target,key,sharedPropertyDefinition)}
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){try{this.cb.call(this.vm,value,oldValue)}catch(e){handleError(e,this.vm,`callback for watcher "${this.expression}"`)}}else{this.cb.call(this.vm,value,oldValue)}}}}
// watcher.jsif(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)}}
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}}
在Vue的每个组件都有一个渲染
watcher
,它会被模版用到的数据作为依赖收集,在状态发生变化时,会通知该watcher
,从而使组件重新执行render
和patch
,最后渲染最新的视图。组件中除了渲染watcher
之外,还有计算属性computed
和监听属性watch
,它们在Vue内部也是watcher
的一种。计算属性
Vue的计算属性常用在模版,并且它的值由响应对象的属性计算而来。来看下面例子:
上面的例子其实可以用方法实现,但是计算属性和方法的区别就是具有缓存的机制,而方法在每次
render
的时候都会重新执行一遍。现在我们通过源码来看看Vue是怎么设计计算属性的。计算属性的初始化是在
src/core/instance/state.js
中的initComputed
函数:这个方法先创建一个空对象
vm._computedWatchers
存储实例的所有计算watcher
,然后遍历计算属性,把计算函数作为getter
新建对应的计算watcher
。这里和渲染watcher
的一个区别就是它有一个配置项{ lazy: true }
, 在构造函数会走下面逻辑:很显然,新建一个计算
watcher
不会马上执行get
方法。接下来,判断如果计算属性的key不存在实例上,会调用defineComputed
方法定义计算属性,否则判断是否和data
或者props
重名。对于组件的计算属性,调用
defineComputed
方法是在新建组件构造器是定义的,它把计算属性挂载到构造器的原型对象。这样做的目的是防止每次新建组件实例都去重新定义一遍。它定义在Vue.extend()
中:现在来看下
defineComputed
方法的定义:这个方法主要就是把计算属性定义在target上,并且通过
createComputedGetter
方法设置getter,对于setter
用户可以自定义否则为空函数noop
。现在我们看下createComputedGetter
是怎么定义的:这个方法返回了计算属性的
getter
函数。当我们正在执行渲染watcher
的get
方法时,对于组件模版访问到的计算属性,就会触发这个getter
。首先,通过实例上
_computedWatchers
获取到计算watcher
。如果watcher.dirty
为true
,就是执行evaluate
方法:所以,计算
watcher
的求值会在模版render
的时候去调用,然后把this.dirty
设置为false
,所以下面再次访问计算属性会直接返回之前保存的值。那么什么时候会还原成true
呢?那当然是计算属性依赖的状态数据发生改变时,接下来我们会分析到。另外一个注意的是,在执行计算
watcher
的get
方法时,也就是执行计算函数。比如我们例子中的:在执行这个函数时,用到的firstName和secondName状态会触发
getter
,然后把fullName的计算watcher
作为依赖进行收集。在计算属性求完值后,会执行下面逻辑:这端代码的意思就是让计算属性依赖的状态收集当前的
Dep.target
。很明显,当前的Dep.target
就是组件的渲染watcher
。所以我们的例子的firstName和secondName都有两个依赖,分别是计算watcher
和渲染watcher
。这样我们的组件就渲染结束,接下来就看看当计算属性的依赖状态发生改变时Vue的处理。比如我们例子点击后会触发
this.firstName = 'wozien1'
,然后会执行firstName状态的setter
通知依赖,调用依赖的update
方法:对于第一个依赖是计算
watcher
,它的lazy
为true
,所以只是简单的把dirty
重新设置为true
就结束了。然后到了渲染watcher
,它会执行queueWatcher(this)
在下个tick中执行run
方法:执行
run
方法组件就会重新render
,这个时候计算属性由于dirty
已经重置为true
,所以会执行evaluate
获取最新的值来渲染视图。因此,我们的状态改变导致计算属性变化进而更新视图的流程就结束了。你会发现,如果我们的状态设置成和之前一样的值,这个时候不会触发状态setter
,也就是计算watcher
的dirty
还是为false
,所以计算属性还是缓存了之前的值。说白了就是,如果模版中用到了计算属性,那么计算属性依赖的状态的改变必然会引起模版的变化,所以把渲染
watcher
收集进状态的dep即可。然后在触发渲染watcher
要把计算watcher
的缓存标记dirty
设置为true
,获取计算属性最新值。监听属性
对于监听属性的入口是定义
src/core/instance/state.js
中的initWatch
方法:这个方法主要遍历实例的
watch
配置调用createWatcher
方法。因为Vue允许我们为一个watch
添加多个handler
,所以要处理handler
是数组的情况。首先处理下
watch
的配置是一个对象的情况,获取对象的handler
最后调用vm.$watch
方法。这个方法是stateMixins
挂载到原型上的:这个方法为实例的监听属性新建一个
watcher
。如果expOrFn是一个函数的话,它会作为watcher
的getter
。否则会执行下面的代码:parsePath
方法是获取字符串形式的对象属性,并且返回一个获取的函数:这个方法就是把字符串按
.
切割,然后循环就可以访问到最终监听的属性。这样两种方式的监听属性的getter
都可以拿到,在执行getter
后就会把该watcher
作为依赖被监听的属性收集。要注意的是,如果监听属性是一个函数,则这个函数里面访问的属性都会收集这个watcher
。然后在状态发生改变时,就会通知这个watcher
执行update
,最后在下个tick中执行对应的回调函数。接下来对于配置了
immediate
为true
的属性,马上执行回调函数。最后返回一个可以卸载这个watcher
的函数,卸载watcher
主要是调用teardown
方法:这个方法首先把自己从实例中移除,然后循环从订阅的Dep中移除。因为我们的
watcher
回调函数是在下一tick执行的,也就是异步执行的。如果要求回调函数在当前的执行栈中执行,可以设置监听属性的sync
为true
,然后update
方法最直接调用run
方法,而不是丢进watcher
队列:另外一种情况,如果我们监听的是一个对象,并且我们想要对象的子属性发生变化也要触发回调函数。我们可以设置
deep
为true
:现在来看下Vue对
deep
处理。在执行watcher
的get
时候有这样一段逻辑:traverse
方法主要是循环访问value的属性,触发属性的getter
,因为当前Dep.target
指向这个监听watcher
,所以value的属性也会收集这个watcher
,从而子属性改变时就会触发监听的回调函数。总结
现在,我们对Vue的所有类型的
watcher
都分析完了,其实它们的背后逻辑都是一样的。就是把watcher
自身作为依赖收集进状态的Dep,在状态发生改变时,执行回调函数或重新渲染组件。The text was updated successfully, but these errors were encountered: