之前已经将数据劫持已经全部完成了。
那么,接下来,主要的要点就是在于两点,依赖收集和触发依赖更新。
它的意义主要在于控制哪些地方使用了这个变量,然后,按照最小的开销来更新视图。
首先,要先明白,依赖是什么,比方说在我们的模板中有 {{a}}
,那么,这个地方就有对于变量 a 的依赖。
在模板编译的时候,就会触发 a 变量的 getter
。
然后,当我们执行 a++;
的时候,那么,我们就要触发依赖的更新,当初模板中 {{a}}
的地方,就要更新,是吧!
所以,我们都是在 getter
中收集依赖,在 setter
中触发依赖更新 。
这一节的内容,主要就是用来专门讲清楚这两件事情。
依赖收集和派发更新
依赖收集和触发依赖更新主要由两个类来完成, Dep
和 Watcher
。
Dep
和 Watcher
在设计模式中,就是发布-订阅者的模式。
而依赖,你可以理解为所谓的订阅者。
- Dep
Dep
说白了就是发布者,它的工作就是依赖管理,要知道哪些地方用到了这个变量,可能用到这个变量的地方有很多,所以,它会有多个订阅者。
然后,每个变量都应该有属于自己的 Dep
,因为每个变量所在的依赖位置是不一样的,所以他们的订阅者也不一样。
然后在变量更新之后,就去通知所有的订阅者(Watcher),我的变量更新了,你们该触发视图更新了。
- Watcher
Watcher
说白了就是订阅者,它接受 Dep
发过来的更新通知之后,就去执行视图更新了。
它其实就是所谓的 watch 监听器,变量改变之后,执行一个回调函数。
初始化我们的 Dep 类
我们先按照图例来创建我们的 Dep
类
根据我们的需求:
- 首先,它要在初始化的时候,新建一个
subs
数组,用来存储依赖,也就是 Watcher 的实例 -
class Dep{ constructor() { // 用数组存储自己的订阅者 subs 是 subscribes 订阅者的意思 // 这个数组里放的是 Watcher 的实例 this.subs=[] } }
- 它需要有一个
depend()
方法,用于添加依赖,也就是将 Watcher 实例往subs
数组中 push
class Dep{
// 添加依赖
depend(){
// 判断当前是否有需要监听的目标,Dep.target 会被 Wacher 赋值
if(Dep.target){
// 将监听的目标推进 subs 数组
this.subs.push(Dep.target);
}
}
}
- 它需要有一个
notify()
方法,用于通知 Wacher 数据更新了,调用 Wacher 的update()
方法
class Dep{
// 通知所有订阅者
notify(){
// 浅克隆一份
const subs=this.subs.slice();
// 遍历
for(let i=0,l=subs.length;i<l;i++){
// 逐个更新
subs[i].update();
}
}
}
使用我们的 Dep 类
每个属性都要有自己的 Dep Dep
我们在前面也说了,每个属性都应该有它自己的 Dep ,用来管理依赖。
所以,首先,如果我们在 Observer
中创建 Dep,那不就可以了。毕竟 Observer
会遍历到每一个对象。
class Observer{
constructor(obj){
this.dep=new Dep();
// ...
}
}
- 在 getter 中收集依赖
所以,很明显,我们可以在 defineReactive 的 get 中收集依赖
因为有了 if(Dep.target)
的判断,所以,只有绑定 Watcher 的变量触发 getter 时,才会添加依赖。
function defineReactive(obj,key,val) {
let dep=new Dep();
let childOb;
// 判断当前入参个数,两个的话直接返回当前层的对象
if(arguments.length===2){
val=obj[key];
childOb = observe(val)
}
Object.defineProperty(obj,key,{
get(){
// Dep.target 是我们弄的唯一标识,当有这个标识的时候,添加依赖
if(Dep.target){
// 添加依赖
dep.depend();
// 如果有子属性,也要将它加入依赖
if(childOb){
// 给子属性添加依赖
childOb.dep.depend();
}
}
return val;
},
})
}
这个 Dep.target
其实就是 Watcher 的实例
在 setter 中触发依赖更新
所以,很明显,我们可以在 defineReactive 的 set 中收调用 notify()
方法告知 Watcher 实例,数据更新了。
function defineReactive(obj,key,val) {
let dep=new Dep();
let childOb;
// 判断当前入参个数,两个的话直接返回当前层的对象
if(arguments.length===2){
val=obj[key];
childOb = observe(val)
}
Object.defineProperty(obj,key,{
// ...
set(newValue){
val=newValue;
childOb = observe(val)
// notify 切忌 val=newValue 之后,不然在 callback 回调中一直是旧值
dep.notify();
}
})
}
初始化我们的 Watcher 类
首先, Watcher
实例应该大家会相对而言更加好理解点,因为,我们有一个 watch 侦听器,大家一定都很熟悉,这两个其实一样。
我们先按照图例来创建我们的 Watcher
类
根据我们的需求:
首先,它要在初始化的时候,需要传入目标对象 target
, 属性名 expression
, 回调函数 callback
class Watcher{
// target 目标对象
// expression 属性名
// callback 回调函数
// value 属性的值
constructor(target,expression,callback) {
this.target=target;
// parsePath 为一个高阶函数
this.getter=parsePath(expression);
this.callback=callback;
// get为我们之后要写的获取值的方法
this.value=this.get();
}
}
。
parsePath需要我们单独说一下,们要监听到 a.b.c.d
,所以,我们需要下面的这种格式
new Watcher(a,'b.c.d',val=>{
console.log('ok啦',val);
})
所以,这个 get 很明显就有点难度了。 我们需要通过循环 拿到 a.b 然后 .c 然后 .d。
我们将这个方法命名为 parsePath
。
function parsePath(str){
let segments = str.split('.');
return obj=> {
for(let i=0;i<segments.length;i++){
if(!obj) return;
obj=obj[segments[i]];
}
return obj;
}
}
入参接受我们的 b.c.d
,我们可以看到 第一句执行之后 segments=['b','c','d']
,然后进行第二层,这是返回了一个方法,按照循环,那就是 obj=obj.b
=> obj=obj.c
=> obj=obj.d
,所以,就是返回一个对象的 obj.b.c.d,相当于是遍历字符串中的属性树。
它需要有一个 get()
方法,用于获取当前的值,并将它更新,然后 return
返回
class Watcher{
// 获取当前的值,并将它更新,然后 return 返回
get(){
// 进入依赖收集阶段,将 Dep.target 设为 Watcher 实例本身
Dep.target=this;
// 当前对象
const obj=this.target;
let value;
// 当对象不再使用的时候,我们需要将它清空
try{
value=this.getter(obj)
}finally {
Dep.target=null;
}
this.value=value
return value;
}
}
它需要有一个 update()
方法,用于执行数据触发更新之后,保存新的值和旧的值,将它返回给 callback
回调函数
class Watcher{
// Dep发过来的通知,当前变量更新了,我们返回一个更新之后的回调函数
update(){
// this.value 由于还没触发更新,所以此时是旧的值
const oldValue=this.value;
// 通过我们的 getter 方法,直接获取最新的值
const newValue=this.get();
// 将新值和旧值返回给 callback 回调函数
this.callback(newValue,oldValue);
}
}
observe(a)
首先, observe(a)
会将 a 对象变为响应式对象
new Watcher
执行 new Watcher
之后,就会调用 Watcher 类的 constructor 。此时 target
是 a , expression
是 'b.c.d', callback
是 (val,oldValue)=>{console.log('ok',val,oldValue); })
// target 目标对象
// expression 属性名
// callback 回调函数
// value 属性的值
constructor(target,expression,callback) {
this.target=target;
// parsePath 为一个高阶函数
this.getter=parsePath(expression);
this.callback=callback;
// get为我们之后要写的获取值的方法
this.value=this.get();
}
this.value=this.get()
又会执行 get()
方法, 此时 Dep.target
被赋值了,就是当前 Watcher 实例。
get(){
// 进入依赖收集阶段,将 Dep.target 设为 Watcher 实例本身
Dep.target=this;
// 当前对象
const obj=this.target;
let value;
// 当对象不再使用的时候,我们需要将它清空
try{
value=this.getter(obj)
}finally {
Dep.target=null;
}
this.value=value
return value;
}
将当前 Watcher 实例推进了 subs
数组中。
// 添加依赖
depend(){
// 判断当前是否有需要监听的目标,Dep.target 会被 Wacher 赋值
if(Dep.target){
// 将 Watcher 实例添加进 subs
this.subs.push(Dep.target)
}
}
触发 defineReactive
中的 set
方法,然后执行 dep.notify();
set(newValue){
val=newValue;
childOb = observe(val)
// notify 切忌 val=newValue 之后,不然在 callback 回调中一直是旧值
dep.notify();
}
通过遍历 subs
列表,通知所有订阅者
// 通知所有订阅者
notify(){
// 浅克隆一份
const subs=this.subs.slice();
// 遍历
for(let i=0,l=subs.length;i<l;i++){
// 逐个更新
subs[i].update();
}
}
相应的订阅者执行 update()
,将新值和旧值获取,然后通过 callback
回调函数返回
// Dep发过来的通知,当前变量更新了,我们返回一个更新之后的回调函数
update(){
// this.value 由于还没触发更新,所以此时是旧的值
const oldValue=this.value;
// 通过我们的 getter 方法,直接获取最新的值
const newValue=this.get();
// 将新值和旧值返回给 callback 回调函数
this.callback(newValue,oldValue);
}
最终 new Watcher
实例中的回调函数成功执行,并且成功拿到 val
和 oldValue
new Watcher(a,'b.c.d',(val,oldValue)=>{
console.log('ok',val,oldValue); // ok 10 5
})
最终代码
class Dep{
constructor() {
// 用数组存储自己的订阅者 subs 是 subscribes 订阅者的意思
// 这个数组里放的是 Watcher 的实例
this.subs=[]
}
// 添加依赖
depend(){
// 判断当前是否有需要监听的目标,Dep.target 会被 Wacher 赋值
if(Dep.target){
// 将 Watcher 实例添加进 subs
this.subs.push(Dep.target)
}
}
// 通知所有订阅者
notify(){
// 浅克隆一份
const subs=this.subs.slice();
// 遍历
for(let i=0,l=subs.length;i<l;i++){
// 逐个更新
subs[i].update();
}
}
}
class Watcher{
// target 目标对象
// expression 属性名
// callback 回调函数
// value 属性的值
constructor(target,expression,callback) {
this.target=target;
// parsePath 为一个高阶函数
this.getter=parsePath(expression);
this.callback=callback;
// get为我们之后要写的获取值的方法
this.value=this.get();
}
// Dep发过来的通知,当前变量更新了,我们返回一个更新之后的回调函数
update(){
// this.value 由于还没触发更新,所以此时是旧的值
const oldValue=this.value;
// 通过我们的 getter 方法,直接获取最新的值
const newValue=this.get();
// 将新值和旧值返回给 callback 回调函数
this.callback(newValue,oldValue);
}
// 获取当前的值,并将它更新,然后 return 返回
get(){
// 进入依赖收集阶段,将 Dep.target 设为 Watcher 实例本身
Dep.target=this;
// 当前对象
const obj=this.target;
let value;
// 当对象不再使用的时候,我们需要将它清空
try{
value=this.getter(obj)
}finally {
Dep.target=null;
}
this.value=value
return value;
}
}
function parsePath(str){
let segments = str.split('.');
return obj=> {
for(let i=0;i<segments.length;i++){
if(!obj) return;
obj=obj[segments[i]];
}
return obj;
}
}
let a={
b:{
c:{
d:10
}
}
}
observe(a)
new Watcher(a,'b.c.d',(val,oldValue)=>{
console.log('ok',val,oldValue); // ok 10 5
})
a.b.c.d=55;