Vue当中响应式 对Dep和Watcher详细解释(手搓)

本文详细解释了JavaScript中Vue.js框架如何通过Dep和Watcher实现依赖收集和触发视图更新的过程,涉及getter和setter中的操作以及Observer和Watcher类的作用。
摘要由CSDN通过智能技术生成

之前已经将数据劫持已经全部完成了。

那么,接下来,主要的要点就是在于两点,依赖收集和触发依赖更新。

它的意义主要在于控制哪些地方使用了这个变量,然后,按照最小的开销来更新视图

首先,要先明白,依赖是什么,比方说在我们的模板中有 {{a}} ,那么,这个地方就有对于变量 a 的依赖。

在模板编译的时候,就会触发 a 变量的 getter

然后,当我们执行 a++; 的时候,那么,我们就要触发依赖的更新,当初模板中 {{a}} 的地方,就要更新,是吧!

所以,我们都是getter 中收集依赖,在 setter 中触发依赖更新

这一节的内容,主要就是用来专门讲清楚这两件事情。

依赖收集和派发更新

依赖收集和触发依赖更新主要由两个类来完成, DepWatcher

DepWatcher 在设计模式中,就是发布-订阅者的模式。

而依赖,你可以理解为所谓的订阅者。

  • 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();
    // ...
  }
}
  1. 在 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;

  • 33
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

来都来了_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值