class RefImpl {
get value() {
this.dep.track();
return this._value;
}
}
从上面可以看到在get拦截中直接调用了dep依赖的track
方法进行依赖收集。
在执行track
方法之前我们思考一下当前响应式系统中有哪些角色,分别是Sub1
和Sub2
这两个watchEffect
回调函数订阅者,以及counter1
和counter2
这两个Dep依赖。此时的响应式模型如下图:![](http://mmbiz.qpic.cn/mmbiz_png/8hhrUONQpFsGdQbVPxmvp5ibIve1zT5FN3HKzyxFbv2EBXuibScbCCEicma2BxXH2NIr7n2eWB8TaibwxMKC4ze5rg/640?wx_fmt=png&from=appmsg)
从上图可以看到此时只有X坐标轴的Dep依赖,以及Y坐标轴的Sub订阅者,没有一个Link节点。
我们接着来看看dep依赖的track
方法,核心代码如下:
class Dep {
// 指向Link链表的尾部节点
subs: Link;
track() {
let link = new Link(activeSub, this);
if (!activeSub.deps) {
activeSub.deps = activeSub.depsTail = link;
} else {
link.prevDep = activeSub.depsTail;
activeSub.depsTail!.nextDep = link;
activeSub.depsTail = link;
}
addSub(link);
}
}
从上面的代码可以看到,每执行一次track
方法,也就是说每次收集依赖,都会执行new Link
去生成一个Link节点。
并且传入两个参数,activeSub
为当前active的订阅者,在这里就是Sub1
(第一个watchEffect
)。第二个参数为this
,指向当前的Dep依赖对象,也就是Dep1
(counter1
变量)。
先不看track
后面的代码,我们来看看Link
这个class的代码,核心代码如下:
class Link {
// 指向Link链表的后一个节点(X轴)
nextDep: Link;
// 指向Link链表的前一个节点(X轴)
prevDep: Link;
// 指向Link链表的下一个节点(Y轴)
nextSub: Link;
// 指向Link链表的上一个节点(Y轴)
prevSub: Link;
- constructor(public sub: Subscriber, public dep: Dep) {
// ...省略
}
}
细心的小伙伴可能发现了在Link
中没有声明sub
和dep
属性,那么为什么前面我们会说Link节点中的sub
和dep
属性分别指向Sub订阅者和Dep依赖呢?
因为在constructor构造函数中使用了public
关键字,所以sub
和dep
就作为属性暴露出来了。
执行完let link = new Link(activeSub, this)
后,在响应式系统模型中初始化出来第一个Link节点,如下图:![](http://mmbiz.qpic.cn/mmbiz_png/8hhrUONQpFsGdQbVPxmvp5ibIve1zT5FNuiaxdiatNd3kHZHmurgSfDszC9KoibacdAiat27HKOI5DicbxYLC5NHRTCA/640?wx_fmt=png&from=appmsg)
从上图可以看到Link1
的sub
属性指向Sub1
订阅者,dep
属性指向Dep1
依赖。
我们接着来看track
方法中剩下的代码,如下:
class Dep {
// 指向Link链表的尾部节点
subs: Link;
track() {
let link = new Link(activeSub, this);
if (!activeSub.deps) {
activeSub.deps = activeSub.depsTail = link;
} else {
link.prevDep = activeSub.depsTail;
activeSub.depsTail!.nextDep = link;
activeSub.depsTail = link;
}
addSub(link);
}
}
先来看if (!activeSub.deps)
,activeSub
前面讲过了是Sub1
。activeSub.deps
就是Sub1
的deps
属性,也就是Sub1
队列上的第一个Link。
从上图中可以看到此时的Sub1
并没有箭头指向Link1
,所以if (!activeSub.deps)
为true,代码会执行
activeSub.deps = activeSub.depsTail = link;
deps
和depsTail
属性分别指向Sub1
队列的头部和尾部,当前队列中只有Link1
这一个节点,那么头部和尾部当然都指向Link1
。
执行完这行代码后响应式模型图就变成下面这样的了,如下图:![](http://mmbiz.qpic.cn/mmbiz_png/8hhrUONQpFsGdQbVPxmvp5ibIve1zT5FNiavsvd5h983OhgWMT5d3MOkfmnniah7XO5oaANRqibVKNkiaiaGvICnA8qQ/640?wx_fmt=png&from=appmsg)
从上图中可以看到Sub1
的队列中只有Link1
这一个节点,所以队列的头部和尾部都指向Link1
。
处理完Sub1
的队列,但是Dep1
的队列还没处理,Dep1
的队列是由addSub(link)
函数处理的。addSub
函数代码如下:
function addSub(link: Link) {
const currentTail = link.dep.subs;
if (currentTail !== link) {
link.prevSub = currentTail;
if (currentTail) currentTail.nextSub = link;
}
link.dep.subs = link;
}
由于Dep1
队列中没有Link节点,所以此时在addSub
函数中主要是执行第三块代码:link.dep.subs = link
。`
link.dep
是指向Dep1
,前面我们讲过了Dep依赖的subs
属性指向队列的尾部。所以link.dep.subs = link
就是将Link1
指向Dep1
的队列的尾部,执行完这行代码后响应式模型图就变成下面这样的了,如下图:![](http://mmbiz.qpic.cn/mmbiz_png/8hhrUONQpFsGdQbVPxmvp5ibIve1zT5FNaDeX4Rfd6aTFVfBicMNW2YZBqa9pbPNOvjVaOTSrvQ3ia0hLBPnXVUbA/640?wx_fmt=png&from=appmsg)
到这里对第一个响应式变量counter1
进行读操作进行的依赖收集就完了。
第一个watchEffect
对counter2
进行读操作
在第一个watchEffect中接着会对counter2
变量进行读操作。同样会走到get
拦截中,然后执行track
函数,代码如下:
class Dep {
// 指向Link链表的尾部节点
subs: Link;
track() {
let link = new Link(activeSub, this);
if (!activeSub.deps) {
activeSub.deps = activeSub.depsTail = link;
} else {
link.prevDep = activeSub.depsTail;
activeSub.depsTail!.nextDep = link;
activeSub.depsTail = link;
}
addSub(link);
}
}
同样的会执行一次new Link(activeSub, this)
,然后把新生成的Link2
的sub
和dep
属性分别指向Sub1
和Dep2
。执行后的响应式模型图如下图:![](http://mmbiz.qpic.cn/mmbiz_png/8hhrUONQpFsGdQbVPxmvp5ibIve1zT5FNs2HaLqfeialJ1MPWJYe1h39E5lBaHI8XJ2kfsvCIF1SCvxxpSsR1pbw/640?wx_fmt=png&from=appmsg)
从上面的图中可以看到此时Sub1
的deps
属性是指向Link1
的,所以这次代码会走进else
模块中。else
部分代码如下:
link.prevDep = activeSub.depsTail;
activeSub.depsTail.nextDep = link;
activeSub.depsTail = link;
activeSub.depsTail
指向Sub1
队列尾部的Link,值是Link1
。所以执行link.prevDep = activeSub.depsTail
就是将Link2
的prevDep
属性指向Link1
。
同理activeSub.depsTail.nextDep = link
就是将Link1
的nextDep
属性指向Link2
,执行完这两行代码后Link1
和Link2
之间就建立关系了。如下图:![](http://mmbiz.qpic.cn/mmbiz_png/8hhrUONQpFsGdQbVPxmvp5ibIve1zT5FNd97lGDZWn8vM0e6JwflINPcmdZibUZ4nd83u1ibBWGVviaPVQRSz43icng/640?wx_fmt=png&from=appmsg)
从上图中可以看到此时Link1
和Link2
之间就有箭头连接,可以互相访问到对方。
最后就是执行activeSub.depsTail = link
,这行代码是将Sub1
队列的尾部指向Link2
。执行完这行代码后模型图如下:![](http://mmbiz.qpic.cn/mmbiz_png/8hhrUONQpFsGdQbVPxmvp5ibIve1zT5FNq2jxSmpR7YbGNW2o5jsFMBnFIeXE9KAw04P6PPhADhpnBZGgMn9lRA/640?wx_fmt=png&from=appmsg)
Sub1
订阅者的队列就处理完了,接着就是处理Dep2
依赖的队列。Dep2
的处理方式和Dep1
是一样的,让Dep2
队列的队尾指向Link2
,处理完了后模型图如下:![](http://mmbiz.qpic.cn/mmbiz_png/8hhrUONQpFsGdQbVPxmvp5ibIve1zT5FNjZkz3FqEZAUhHWC9zycyf6vvYfZSlk45bA9icQnbibFUPhJ9IYafvEoA/640?wx_fmt=png&from=appmsg)
到这里第一个watchEffect(也就是Sub1
)对其依赖的两个响应式变量counter1
(也就是Dep1
)和counter2
(也就是Dep2
),进行依赖收集的过程就执行完了。
第二个watchEffect
对counter1
进行读操作
接着我们来看第二个watchEffect
,同样的还是会对counter1
进行读操作。然后触发其get
拦截,接着执行track
方法。回忆一下track
方法的代码,如下:
class Dep {
// 指向Link链表的尾部节点
subs: Link;
track() {
let link = new Link(activeSub, this);
if (!activeSub.deps) {
activeSub.deps = activeSub.depsTail = link;
} else
{
link.prevDep = activeSub.depsTail;
activeSub.depsTail!.nextDep = link;
activeSub.depsTail = link;
}
addSub(link);
}
}
这里还是会使用new Link(activeSub, this)
创建一个Link3
节点,节点的sub
和dep
属性分别指向Sub2
和Dep1
。如下图:![](http://mmbiz.qpic.cn/mmbiz_png/8hhrUONQpFsGdQbVPxmvp5ibIve1zT5FNSiaENkwWbjMicl0QAzPVMzRrYJPGg4dc8lxibIbIRdJvP5ErQwdGdOtibg/640?wx_fmt=png&from=appmsg)
同样的Sub2
队列上此时还没任何值,所以if (!activeSub.deps)
为true,和之前一样会去执行activeSub.deps = activeSub.depsTail = link;
将Sub2
队列的头部和尾部都设置为Link3
。如下图:![](http://mmbiz.qpic.cn/mmbiz_png/8hhrUONQpFsGdQbVPxmvp5ibIve1zT5FNFWialxpRqmmA4S0C1tHvapZfPyWtcvy0hSIJ7SrP75SLdhOnoia8lR1g/640?wx_fmt=png&from=appmsg)
处理完Sub2
队列后就应该调用addSub
函数来处理Dep1
的队列了,回忆一下addSub
函数,代码如下:
function addSub(link: Link) {
const currentTail = link.dep.subs;
if (currentTail !== link) {
link.prevSub = currentTail;
if (currentTail) currentTail.nextSub = link;
}
link.dep.subs = link;
}
link.dep
指向Dep1
依赖,link.dep.subs
指向Dep1
依赖队列的尾部。从前面的图可以看到此时队列的尾部是Link1
,所以currentTail
的值就是Link1
。
if (currentTail !== link)
也就是判断Link1
和Link3
是否相等,很明显不相等,就会走到if的里面去。
接着就是执行link.prevSub = currentTail
,前面讲过了此时link
就是Link3
,currentTail
就是Link1
。执行这行代码就是将Link3
的prevSub
属性指向Link1
。
接着就是执行currentTail.nextSub = link
,这行代码是将Link1
的nextSub
指向Link3
。
执行完上面这两行代码后Link1
和Link3
之间就建立联系了,可以通过prevSub
和nextSub
属性访问到对方。如下图:![](http://mmbiz.qpic.cn/mmbiz_png/8hhrUONQpFsGdQbVPxmvp5ibIve1zT5FN2BMvv3DzjwmbkW9pZnUnFqWkrEZyex3m14CgOE8MuCKorzBHTMWxng/640?wx_fmt=png&from=appmsg)
接着就是执行link.dep.subs = link
,将Dep1
队列的尾部指向Link3
,如下图:![](http://mmbiz.qpic.cn/mmbiz_png/8hhrUONQpFsGdQbVPxmvp5ibIve1zT5FNPDicdoDK9FrYicQDL3z0dSzkeFJISM0tcuSbIK2ojhLS9F4UjIMn4bBw/640?wx_fmt=png&from=appmsg)
到这里第一个响应式变量counter1
进行依赖收集就完成了。
第二个watchEffect
对counter2
进行读操作
在第二个watchEffect中接着会对counter2
变量进行读操作。同样会走到get
拦截中,然后执行track
函数,代码如下: