深入理解Vue Computed计算属性原理
Computed计算属性是Vue中常用的一个功能,但你理解它是怎么工作的吗?
拿官网简单的例子来看一下:
Originalmessage:"{{message}}"
Computedreversedmessage:"{{reversedMessage}}"
varvm=newVue({ el:'#example', data:{ message:'Hello' }, computed:{ //acomputedgetter reversedMessage:function(){ //`this`pointstothevminstance returnthis.message.split('').reverse().join('') } } })
Situation
Vue里的Computed属性非常频繁的被使用到,但并不是很清楚它的实现原理。比如:计算属性如何与属性建立依赖关系?属性发生变化又如何通知到计算属性重新计算?
关于如何建立依赖关系,我的第一个想到的就是语法解析,但这样太浪费性能,因此排除,第二个想到的就是利用JavaScript单线程的原理和Vue的Getter设计,通过一个简单的发布订阅,就可以在一次计算属性求值的过程中收集到相关依赖。
因此接下来的任务就是从Vue源码一步步分析Computed的实现原理。
Task
分析依赖收集实现原理,分析动态计算实现原理。
Action
data属性初始化gettersetter:
//src/observer/index.js //这里开始转换data的gettersetter,原始值已存入到__ob__属性中 Object.defineProperty(obj,key,{ enumerable:true, configurable:true, get:functionreactiveGetter(){ constvalue=getter?getter.call(obj):val //判断是否处于依赖收集状态 if(Dep.target){ //建立依赖关系 dep.depend() ... } returnvalue }, set:functionreactiveSetter(newVal){ ... //依赖发生变化,通知到计算属性重新计算 dep.notify() } })
computed计算属性初始化
//src/core/instance/state.js //初始化计算属性 functioninitComputed(vm:Component,computed:Object){ ... //遍历computed计算属性 for(constkeyincomputed){ ... //创建Watcher实例 //createinternalwatcherforthecomputedproperty. watchers[key]=newWatcher(vm,getter||noop,noop,computedWatcherOptions) //创建属性vm.reversedMessage,并将提供的函数将用作属性vm.reversedMessage的getter, //最终computed与data会一起混合到vm下,所以当computed与data存在重名属性时会抛出警告 defineComputed(vm,key,userDef) ... } } exportfunctiondefineComputed(target:any,key:string,userDef:Object|Function){ ... //创建getset方法 sharedPropertyDefinition.get=createComputedGetter(key) sharedPropertyDefinition.set=noop ... //创建属性vm.reversedMessage,并初始化gettersetter Object.defineProperty(target,key,sharedPropertyDefinition) } functioncreateComputedGetter(key){ returnfunctioncomputedGetter(){ constwatcher=this._computedWatchers&&this._computedWatchers[key] if(watcher){ if(watcher.dirty){ //watcher暴露evaluate方法用于取值操作 watcher.evaluate() } //同第1步,判断是否处于依赖收集状态 if(Dep.target){ watcher.depend() } returnwatcher.value } } }
无论是属性还是计算属性,都会生成一个对应的watcher实例。
//src/core/observer/watcher.js //当通过vm.reversedMessage获取计算属性时,就会进到这个getter方法 get(){ //this指的是watcher实例 //将当前watcher实例暂存到Dep.target,这就表示开启了依赖收集任务 pushTarget(this) letvalue constvm=this.vm try{ //在执行vm.reversedMessage的函调函数时,会触发属性(步骤1)和计算属性(步骤2)的getter //在这个执行过程中,就可以收集到vm.reversedMessage的依赖了 value=this.getter.call(vm,vm) }catch(e){ if(this.user){ handleError(e,vm,`getterforwatcher"${this.expression}"`) }else{ throwe } }finally{ if(this.deep){ traverse(value) } //结束依赖收集任务 popTarget() this.cleanupDeps() } returnvalue }
上面多出提到了dep.depend,dep.notify,Dep.target,那么Dep究竟是什么呢?
Dep的代码短小精悍,但却承担着非常重要的依赖收集环节。
//src/core/observer/dep.js exportdefaultclassDep{ statictarget:?Watcher; id:number; subs:Array; 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(){ constsubs=this.subs.slice() for(leti=0,l=subs.length;i Result
总结一下依赖收集、动态计算的流程:
1.data属性初始化gettersetter
2.computed计算属性初始化,提供的函数将用作属性vm.reversedMessage的getter
3.当首次获取reversedMessage计算属性的值时,Dep开始依赖收集
4.在执行messagegetter方法时,如果Dep处于依赖收集状态,则判定message为reversedMessage的依赖,并建立依赖关系
5.当message发生变化时,根据依赖关系,触发reverseMessage的重新计算
到此,整个Computed的工作流程就理清楚了。Vue是一个设计非常优美的框架,使用GetterSetter设计使依赖关系实现的非常顺其自然,使用计算与渲染分离的设计(优先使用MutationObserver,降级使用setTimeout)也非常贴合浏览器计算引擎与排版引擎分离的的设计原理。
如果你想成为一名架构师,不能只停留在框架的API使用层面。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。