vue的computed是一个比较常用的功能,一般是用来获取一个或多个变量的组合,简单用法如下:

上面的demo中c返回的是a和b的和,这个很容看出来,关键是a或者b如果改变的话,c也会同时被改变,那么内部是如何做到这种关联的呢?

以前我以为可能是通过字符串匹配,匹配c里面出现的this,然后进行绑定关联,但是我想这种做法未免太蠢?然后最近刚好遇到一个问题(对computed如何实现不确定导致),更加坚信让我剖析computed的内部实现原理。

Dep,Watcher

vue中有两个很重要的概念,Dep和Watcher。Dep其实是一个订阅者模式,它可以增加订阅(depend)以及发布(notify):

Dep一般和defineProperty一起使用,defineProperty可以定一个object中key的getter和setter,它和Dep完美配合,形成一个组合:当get的时候,执行depend(依赖绑定);当set的时候,进行发布(update)。

background Layer 1 a get set depend notify

Watcher则是一个观察者,它的内部定义了Dep中发布(notify)的方法(update):

上面的代码看起来有些多,有些吃力,其实它也是一个演示代码而已,里面的addDep,depend,cleanupDeps是一些增加依赖,删除依赖(与Dep的依赖)的方法,至于里面的depIds,newDepIds,newDeps,deps不用考虑那么多,只要知道是存储依赖的就好了;dirty表示该watcher是否是“脏”的,例如上面的c变量,如果a改变了,那么c对应watcher中的dirty将变成true,也就是被污染的意思,一般被污染了就需要重新获取值。

Dep和Watcher关联

上面讲了一些关于Dep和Watcher的基础定义,到这里可能还是一头雾水,到底在干嘛?下面通过一个简单的例子,就是模拟vue的watch功能:

上面定义了一个hero对象,然后initData将hero的所有属性进行拆解,每个属性的getter和setter与Dep关联;watch方法则是将每个属性和一个Watcher关联,我们可以图示它们的关系:

background Layer 1 a get set depend notify Dep&defineProperty a Watcher Deps:[ ] update:()=>{/**/}

整体上是分成两个部分,Dep区和Watcher区,左边通过initData分解的机构,它们属于Dep区;右边则是新建的Watcher,它们属于Watcher区。

现在关键问题是为啥hero.a=10;的时候为啥会输出“a change from [111] to [10]”?

我们顺着new Watcher的代码看,可以发现它执行了一个get()方法:

执行pushTarget(this),它会将自己这个Watcher赋值成了Dep.target,也就是一个全局环境里面:

background Layer 1 a get set depend notify Dep&defineProperty a Watcher Deps:[ ] update:()=>{/**/} pushTarget(this) Dep.target:

然后会执行this.getter.call(this.data, this.data),之前我们说过对象执行getter的时候会执行依赖绑定操作:

background Layer 1 a get set depend notify Dep&defineProperty a Watcher Deps:[ ] update:()=>{/**/} Dep.target:

depend()会将当前的Watcher(Dep.target)和Dep进行绑定关联:在Dep中存入当前的Watcher,当前的Watcher中存入Dep。

background Layer 1 a get set depend notify Dep&defineProperty a Watcher Deps: update:()=>{/**/} a a

至此,Dep和Watcher的关联就已经确定了,而且这种关系是互通的(好比双方都互留了电话一样)。执行完后,会将当前的Dep.targe抹去(finally中执行)。

这样,当执行hero.a的时候,会先执行Dep中的notify,然后调用Watcher中的update方法,update里面会执行回调方法,因此可以输出“a change from [111] to [10]”。

computed

上面已经透过watch,了解了Dep和Watcher的绑定过程,那么现在来看下computed:

computed会遍历所有key(传入一个对象),每个key建立一个Watcher,但是这个Watcher和一个defineProperty相关联:

background Layer 1 a get set depend notify Dep&defineProperty c Watcher Deps:[ ] b get set depend notify get evaluate

和上面的绑定不同,computed需要先调用getter才可以触发绑定操作(之前的new Watcher之后就开始自动执行绑定操作了)。

运行hero.c;那么就可以开始进行绑定操作了,主要执行的就是watcher.evaluate();而evaluate()内部,调用的也是get()方法:

background Layer 1 a get set depend notify Dep&defineProperty c Watcher Deps:[ ] b get set depend notify get evaluate Dep.target:

上面这个操作是不是比较眼熟,是的,它执行的是get()方法内部的pushTarget(this),将Watcher本身放置在全局变量中。

background Layer 1 a get set depend notify Dep&defineProperty c Watcher Deps:[ ] b get set depend notify get evaluate Dep.target:

接下来就是重头戏了,我们之前将的绑定是一次只能绑定一个,但是这里为什么能一次绑定两个?

触发Dep发生getter的核心代码是this.getter.call(this.data, this.data);之前的this.getter是'a',执行代码为hero.a,会触发a的getter;但是这里的this.getter是function,执行代码为:function(){return this.a+this.b;},会同时触发a和b的getter!!这也正是computed可以做到多对一的关联关系的原因所在。

background Layer 1 a get set depend notify Dep&defineProperty c Watcher Deps: b get set depend notify get evaluate c c a b

至此,a,b和c的对应关系就算关联上了,那么在a或b更新的时候,就会调用watch的update,此时的dirty就会变成true,表示被污染,同时会重新获取c的值(在run()方法中),那么c的function就被执行了,注意之前会执行回调方法,但是在computed中,没有回调方法,只是重新调用get()方法。

其他文章

0
我要评论

评论

返回
×

我要评论

回复:

昵称:(昵称不超过20个字)

图片:

提交
还可以输入500个字