深入理解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使用层面。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。