• Vue:object变化侦测


    变化侦测指的是对数据进行监听当数据发生变化时,对变化的数据重新渲染,实现响应式渲染。在Vue中object和array的变化侦测是不一样的,这一篇主要介绍object的变化侦测。

    object的变化侦测的核心是监听object数据的变化,可以通过Object.defineProperty和Proxy实现,Vue2.x时,ES6支持不是很理想,采用的是Obejct.definedProperty,Vue3已经使用Proxy对数据侦测进行重写。这一篇主要以Vue2.x为例介绍其原理,虽然Vue3使用的是Proxy,但是原理都一样。

    object变化侦测的流程是当对象触发getter时对依赖进行收集,当对象触发setter时触发与该数据相关的所有依赖,就是通知所有使用了该数据的地方。总结为一句话就是:getter收集依赖,setter触发依赖。
    在这里插入图片描述

    既然知道了是使用Object.defineProperty可以侦听到对象的变化,那么就封装一个用于侦听对象变化的函数。

    function defineReactive(data, key, val) {
      Object.defineProperty(data, key, {
        enumerable: true,
        configurable: true,
        set(newVal) {
          if (val === newVal) {
            return;
          }
          val = newVal;
        },
        get() {
          return val;
        }
      });
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    不过只是侦测对象的变化还不够,还得需要在数据变化时,做一些其他的处理,才能让数据动态显示。接下来对该函数做一些改造。

    function defineReactive(data, key, val) {
    	let dep = [];
      Object.defineProperty(data, key, {
        enumerable: true,
        configurable: true,
        set(newVal) {
          if (val === newVal) {
            return;
          }
           for(let i = 0; i < dep.length; i++){
    		dep[i](newVal, val);		
    	  }
          val = newVal;
        },
        get() {
          dep.push(function(){});
          return val;
        }
      });
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    这样就可以在数变化的时候在dep中的函数执行自定义逻辑。接下来,进行一些优化,把自定义的逻辑回调函数,分离出来,单独进行管理。
    这个Dep类专门用来管理回调函数。

    class Dep {
      constructor() {
        // 用来存储回调函数
        this.subs = [];
      }
      // 添加回调函数辅助函数
      addSub(sub) {
        this.subs.push(sub);
      }
      // 追加回调函数
      dependSub() {
        // 为undefined时不再添加,否则会一直添加导致死循环
        if (window.target) {
        // 添加固定的属性,值可以改变
          this.addSub(window.target);
        }
      }
      // 移除回调函数
      removeSub(sub) {
        this.remove(this.subs, sub);
      }
      // 执行回调函数
      notify() {
        let subs = this.subs.slice(0);
        for (let i = 0; i < subs.length; i++) {
          subs[i].update();
        }
      }
    }
    // 移除回调函数辅助函数
    function remove(subs, sub) {
      for (let i = 0; i < subs.length; i++) {
        if (sub === subs[i]) {
          subs.splice(i, 1);
          return;
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    然后对defineReactive函数进行改造

    function defineReactive(data, key, val) {
      let dep = new Dep();
      Object.defineProperty(data, key, {
        enumerable: true,
        configurable: true,
        set(newVal) {
          if (val === newVal) {
            return;
          }
          val = newVal;
          dep.notify();
        },
        get() {
          dep.dependSub();
          return val;
        }
      });
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    接下来对需要收集的依赖进行管理。依赖就是Watcher。Watcher先存储在全局指定的位置,然后读取数据,触发getter操作收集依赖,哪个Watcher触发getter就把哪个Watcher存储在Dep中,在触发getter时,将所有的Watcher都通知一遍。

    class Watcher {
      constructor(vm, expOrFn, cb) {
        // vm实例
        this.vm = vm;
        // 存储用于触发对象getter操作的函数,当执行getter时会访问侦测对象中的属性,以触发对象的getter操作,收集依赖
        this.getter = parsePath(expOrFn);
        // 存储回调函数
        this.cb = cb;
        // 存储属性的当前值
        this.value = this.get();
      }
      // 通过访问对象属性,触发getter操作,开始收集依赖
      get() {
        window.target = this;
        let value = this.getter.call(this.vm, this.vm);
        // 手动设置为undefined,用于是否继续添加依赖的判断
        window.target = undefined;
        return value;
      }
      // 当侦测到对象数据变化会执行该函数
      update() {
        let oldValue = this.value;
        // 存储对象的新值,同时重新收集依赖
        this.value = this.get();
        // 执行回调函数
        this.cb.call(this.vm, this.value, oldValue);
      }
    }
    // 解析变量简单访问路径
    function parsePath(path) {
      let segments = path.split('.');
      return function (obj) {
        for (let i = 0; i < segments.length; i++) {
          if (!obj) return;
          obj = obj[segments[i]];
        }
        return obj;
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    接下来再对对象侦测进行封装。

    class Observer {
      constructor(value) {
        if (!Array.isArray(value)) {
          this.walk(value);
        }
      }
      walk(data) {
        let keys = Object.keys(data);
        // 监听对象的所有属性
        for (let i = 0; i < keys.length; i++) {
          defineReactive(data, keys[i], data[keys[i]]);
        }
      }
    }
    function defineReactive(data, key, val) {
    // 递归监听对象的子属性
      if (typeof val === 'object') {
        new Observer(val);
      }
      let dep = new Dep();
      Object.defineProperty(data, key, {
        enumerable: true,
        configurable: true,
        set(newVal) {
          if (val === newVal) {
            return;
          }
          val = newVal;
          dep.notify();
          console.log('执行依赖');
        },
        get() {
          dep.dependSub();
          return val;
        }
      });
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    后面侦测对象时只需要new 一下即可

    let obj = {}
    new Observer(obj);
    
    • 1
    • 2

    最后可以进行测试,当数据变化时是否能够触发回调函数。

    let vm = {
      data: {
        addr: 'hubeiwuahn'
      },
      $watcher: function (expOrFn, cb) {
        new Watcher(this, "data." + expOrFn, cb);
      }
    }
    new Observer(vm.data);
    vm.$watcher('addr', function (newValue, oldValue) {
      console.log('监听开始', newValue, oldValue);
    });
    vm.data.addr = 'beijing';
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    在这里插入图片描述

  • 相关阅读:
    当小白遇到FullGC
    JAVA String 和 String[][]互转的两种方法
    java-php-python-ssm新城街道社区的健康档案管理平台计算机毕业设计
    GIL全局解释器锁
    园子开店记-起名:万事开头难,起名难上难
    单反相机用sd卡还是cf卡?相机cf卡和sd卡区别
    重温文件操作(一)
    基于 ANFIS 的非线性回归(Matlab代码实现)
    【牛客刷题】bfs和dfs (二叉树层序遍历、矩阵最长递增路径、被围绕的区域)
    Java 设计模式实战系列—策略模式
  • 原文地址:https://blog.csdn.net/qq_40850839/article/details/126157637