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){// $flow-disable-lineconstwatchers=vm._computedWatchers=Object.create(null)// computed properties are just getters during SSRconstisSSR=isServerRendering()for(constkeyincomputed){constuserDef=computed[key]constgetter=typeofuserDef==='function' ? userDef : userDef.get// 拿到计算属性的每一个 userDef,然后尝试获取这个 userDef 对应的 getter 函数,拿不到则在开发环境下报警告if(process.env.NODE_ENV!=='production'&&getter==null){warn(`Getter is missing for computed property "${key}".`,vm)}// 接下来为每一个 getter 创建一个 computed watcherif(!isSSR){// create internal watcher for the computed property.watchers[key]=newWatcher(vm,getter||noop,noop,computedWatcherOptions)}// 最后对判断如果 key 不是 vm 的属性,// 则调用 defineComputed(vm, key, userDef),// 否则判断计算属性对于的 key 是否已经被 data 或者 prop 所占用// 如果是的话则在开发环境报相应的警告。if(!(keyinvm)){defineComputed(vm,key,userDef)}elseif(process.env.NODE_ENV!=='production'){if(keyinvm.$data){// 当属性 在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)}}}}
进入 defineComputed 方法
constsharedPropertyDefinition={enumerable: true,configurable: true,get: noop,set: noop}// 其实就是利用 Object.defineProperty 给计算属性对应的 key 值添加 getter 和 setterexportfunctiondefineComputed(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)}
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}
/src/core/observer/traverse.js
traverse 函数的逻辑也很简单,它实际上就是对一个对象做深层递归遍历
因为遍历过程中就是对一个子对象的访问,会触发它们的 getter 过程
这样就可以进行一波依赖收集,订阅它们的 watcher
这里面明显会有一定的性能开销,所以一定要根据应用场景权衡是否要开启 deep
计算属性 VS 侦听属性
Vue 的组件对象支持了计算属性 computed 和侦听属性 watch 2 个选项
很多人不了解什么时候该用 computed 什么时候该用 watch。
我们从源码实现的角度来分析它们两者有什么区别。
computed
计算属性发生在
vue
的初始化阶段initState
函数中。Vue 初始化主要就干了几件事情,合并配置,初始化生命周期,初始化事件中心,初始化渲染。
初始化
data、props、computed、watcher
等等。 然后执行钩子函数callHook(vm, 'created')
所以我们在 created 钩子函数里面可以拿到以上数据!
我们主要关注一下
initComputed
方法进入
defineComputed
方法进入
createComputedGetter
方法可以发现
computed watcher
会并不会立刻求值,同时持有一个dep
实例然后当我们访问
this.fullName
就会出发计算属性的getter
他就会拿到
watcher
并且执行收集的依赖depend()
最后通过 return this.value 拿到计算属性对应的值。
watch
侦听属性的初始化也是发生在 Vue 的实例初始化阶段的 initState 函数中,在 computed 初始化之后。
API:
{ [key: string]: string | Function | Object | Array }
可以是数组,所以有一个遍历的过程createWatcher
侦听属性
watch
最终会调用$watch
方法 这里需要注意一点options.user = true
通过源码可知
watch
有四种属性deep user lazy sync
说过了
computed watcher
我们看deep watcher
traverse 函数的逻辑也很简单,它实际上就是对一个对象做深层递归遍历
因为遍历过程中就是对一个子对象的访问,会触发它们的
getter
过程这样就可以进行一波依赖收集,订阅它们的
watcher
这里面明显会有一定的性能开销,所以一定要根据应用场景权衡是否要开启
deep
user watcher
通过
vm.$watch
创建的watcher
是一个user watcher
,它的功能很简单,在对 watcher 求值以会处理一下异常情况最后是
sync watcher
没啥说的通过对面源码的分析我们对计算属性和侦听属性的实现有了深入的了解
计算属性本质上是
computed watcher
而侦听属性本质上是
user watcher
。就应用场景而言,计算属性适合用在模板渲染中,某个值是依赖了其它的响应式对象甚至是计算属性计算而来;而侦听属性适用于观测某个值的变化去完成一段复杂的业务逻辑。
最后的最后
大家在面试的时候经常会说,computed 有缓存 watch 没有缓存
那你有没有思考过 computed 是怎么实现缓存的?
也就是我在
createComputedGetter
函数写了注释的地方,我们一起回顾一下这里面当他求值了之后,把 dirty 设置了 false 这说明目前的情况我无论执行多少次
都不会进
watcher.evaluate()
求值这个方法,而是直接返回watcher.value
那么什么时候把
dirty
设置为true
呢 我们可反向推理先搜索代码中设置为
ture
的地方, 我们发现他在update
函数里面这里又要涉及到另外一个知识点 依赖收集 和 触发更新 的 Dep 大概意思就是当一个值被访问的时候
会收集用这个值的地方 我们叫做"依赖",然后当这个值被set新值的时候,会执行收集到的"依赖"
出发的就是上面的 update 导致 this.dirty = true
顺序
1、 调用计算 watcher 的 update
2、 调用渲染 watcher 的 update
然后就会触发重新求值
大致就是这样,前提可能需要理解一点依赖收集的过程。
The text was updated successfully, but these errors were encountered: