• Vue2.0中的$watch、$set、$delete源码解析


    $watch的实现就是对Watcher类的封装,在此基础上实现了{deep: true, immediate: true}可选参数。
    在实际使用中,expOrFn这个参数可以是一个表达式或者是一个函数,表达式只接受以点分隔的路径,比如“a.b.c”。所以当参数是一个函数的时候,Watcher类的构造器里面会直接把该函数赋值给this.getter = expOrFn,否则,就用parsePath函数去解析表达式后赋值给getter。 值得注意的是,如果参数是函数,那么Watcher会同时观察这个函数里读取的实例上的数据,任一数据变动都会得到通知。
    export default class Watcher{
      constructor(vm, expOrFn, cb){
        this.vm = vm;
        if(typeof expOrFn === 'function'){
          this.getter = expOrFn;
        } else{
          this.getter = parsePath(expOrFn)
        }
        this.cb = cb;
        this.value = this.get();
      }
      ......
    }
    immediate参数存在,则直接立即执行一次cb,这个很简单。
    最后unwatchFn顾名思义就是把当前watcher实例从正在观察的依赖列表中移除。怎么在Watcher类里实现需要的时候添加,不需要的时候移除呢?
    就是用一个let depIds = new Set()集合来判断,如果当前Watcher已经订阅了Dep,则不会重复添加订阅。这个判断,可以防止数据变化时Watcher读取新数据时,重复收集依赖。
    export default class Watcher{
      constructor(vm, expOrFn, cb){
        this.vm = vm;
        this.deps = [];
        this.depIds = new Set();
        ......
        this.cb = cb;
        this.value = this.get();
      }
      ......
      addDep(dep){
        const id = dep.id;
        if(!this.depIds.has(id)){
          this.depIds.add(id);  // 记录自己以后订阅了
          this.deps.push(dep);  // 记录自己订阅了哪些Dep
          dep.addSub(this); // 将自己订阅到Dep中
        }
      }
      ......
    }
    在Dep类里面,也会通过window.target.addDep(this)来记录数据发生变化时,要通知哪些Watcher, 所以Watcher和Dep是多对多关系
    那么teardown()做的事就是遍历deps,然后执行this.deps[i].removeSub(this),removeSub()函数就是将Dep里的Watcher从subs数组中移除,所以数据发生变化时,就不会再通知这个Watcher了。
    最后是deep参数的实现逻辑,我们知道deep是深度监听变化,也就是不仅当前数据需要收集依赖,其子数据也要触发依赖收集。那么就在get()函数里, 在window.target=undefined之前,进行递归遍历。递归逻辑很简单。如果不是数组或者对象,或者已经被冻结,那么直接返回什么都不做;接着判断dep.id是否存在,来保证不会重复收集依赖;如果是数组,则遍历数组元素递归调用;如果是Object类型,则通过循环key,递归子值,此时因为会获取子值所以会触发一次getter,也就是触发依赖收集,此时window.target没有清空,所以会执行收集,这也是为什么要写在清空逻辑之前的原因。
    vm.$set(target, key, value)
    对于新增的对象属性无法被追踪到,所以$set函数主要是用来规避这个问题,使新增的属性变为响应式。set实现源码如下:
    首先看对target是数组的处理: 如果索引是有效值,那么先更改length,然后利用splice方法设置对应的值,在执行splice方法的时候,数组拦截器会侦听到数据变化,从而把新增的val转换成响应式,然后返回val。
    如果key已经存在于target,那么直接改value就行了,修改的动作直接就会被侦听到,然后通知依赖。
    如果key是新增的,首先剔除target是vue实例或者根数据对象,然后判断是否有_ob_属性,没有的话说明不是响应式的,那么直接target[key]=val。如果都不满足,说明是在响应式数据上新增属性,就用defineReactive函数转换为getter/setter形式就行。
    vm.$delete(target, key)
    ES6之前,js无法检测到属性从对象上删除,所以使用$delete方法,在删除后想依赖发送消息,通知Watcher数据变化了。这个方法就定义在set函数下面:
    看代码就行,其实大意就是删掉后,然后主动触发依赖更新。
  • 相关阅读:
    前段导出XLSX表格
    基于springboot在线考试报名系统毕业设计源码031706
    docker系列(1) - docker环境篇
    ps怎么把图片变清晰,自学ps软件photoshop2022,简单快速用ps让照片更清晰更有质感
    如何在响应头中防治xss
    基于物联网的防盗报警器设计与实现
    scrapy的安装和使用
    [Leetcode] 0836. 矩形重叠
    【LeetCode第366场周赛】8028. 执行操作使两个字符串相等 | 线性DP | 中等
    PowerPC T2080部分板卡产品介绍
  • 原文地址:https://blog.csdn.net/denglouhen/article/details/125505036