Vue响应式原理五:响应式-自动收集依赖

发布于:2025-07-13 ⋅ 阅读:(15) ⋅ 点赞:(0)

当前存在的问题:一个对象中的所有属性使用同一个Depend对象, 导致无法区分不同属性的依赖关系(是否发生变化)。例如: 例如当obj.name或obj.age变化时,都会通知所有依赖函数

一. 数据结构缺陷

    1. 对于包含name和age属性的obj对象,使用的是同一个Depend对象会同时收集两个属性的reactiveFns,会造成无法区分是那个属性更新了。
    1. 数据结构图如下:
    • 在这里插入图片描述

二. 将数据结构修改多级层次,可以实现自动收集依赖

    1. 实际开发中会不同的对象,另外会有不同的属性需要管理,可以使用一种数据结构来管理不同对象的不同依赖关系;
    1. 通过WeakMap来管理响应式的数据依赖:
      在这里插入图片描述
    1. 数据结构文字描述:objWeakMap对象 -> obj对象 -> map对象 -> key -> dep对象
    • 3.1. WeakMap层:以响应式对象为key(如obj/user),值为第二层Map
    • 3.2. Map层:以对象属性名为key(如name/age),值为对应Dep对象
    • 3.3. Dep层存储该属性对应的所有reactiveFns
    1. 对象依赖管理的实现:
    • 4.1. dep对象管理的数据结构如下:
       // 封装一个函数:负责通过obj的key获取对应的Depend对象
        const objMap = new WeakMap() // WeakMap弱引用
        function getDepend (obj, key) {
          // 1.根据对象obj,找到对应的map对象
          // obj对象 -> map对象 -> dep对象
          // objMap对象 -> obj对象 -> map对象 -> key -> dep对象  
          let map = objMap.get(obj)
          if(!map) {
            map = new Map()
            objMap.set(obj, map)
          }
    
          // 2.根据key,找到对应的depend对象
          let dep = map.get(key)
          if(!dep) {
            dep = new Depend();
            map.set(key, dep)
          }
    
          return dep
        }
    
    • 4.2. 通过代理对象进行数据监听
       // 监听属性变化数据劫持
       // 方案一:Object.defineProperty  -> Vue2
       Object.keys(obj).forEach(key => {
         let value = obj[key];
         Object.defineProperty(obj, key, {
           set: function(newValue) {
             value = newValue;
             // 更新属性值时触发
             // 调用dep.notify()通知所有依赖函数
             const dep = getDepend(obj, key)
             dep.notify()
           },
           get: function() {
             // 拿到obj -> key
             // console.log('get函数中:', obj, key);
             // 找到对应的obj对象的key对应的dep对象
             // 自动收集依赖
             const dep = getDepend(obj, key)
             dep.addDepend(reactiveFn)
             // obj.name -> dep对象 reactiveFns 
             // obj.age -> dep对象 reactiveFns
             return value;
           }
         })
       })
    
  • 5.自动收集原理:

    • 设置全局reactiveFn自由变量存储当前函数
    • 执行响应式函数如果访问属性触发getter收集依赖
    • 执行完成后清空reactiveFn
    • 示例代码如下:
      	let reactiveFn = null // 自由变量
      	function watchFn (fn) {
      	  reactiveFn = fn
      	  fn()
      	  reactiveFn = null 
      	}
      
    1. 完整代码:
    /**
      * 1. dep对象数据结构的管理
          * 每一个对象的每一个属性都会对应一个dep对象
          * 同一个对象的多个属性的dep对象是存放一个map对象中
          * 多个对象的map对象,会被存放到一个objMap的对象中
      * 2. 依赖收集:当执行get函数时,自动的添加fn函数
     */ 
    
    // 实际开发中,响应式对象肯定是不止一个对象的,使用类单独来管理一个对象所有的依赖
    class Depend {
      constructor() {
        this.reactiveFns = [];
      }
    
     depend () {
        if(reactiveFn) {
          this.reactiveFns.add(reactiveFn)
        }
      }
    
      addDepend (fn) {
        if(fn) {
          this.reactiveFns.push(fn);
        }
      }
    
      notify () {
        this.reactiveFns.forEach(fn => {
          fn()
        })
      }
    }
    
    const obj = {
      name: 'why',
      age: 18
    }
    
    // 封装一个函数:负责通过obj的key获取对应的Depend对象
    const objMap = new WeakMap() // WeakMap弱引用
    function getDepend (obj, key) {
      // 1.根据对象obj,找到对应的map对象
      // obj对象 -> map对象 -> dep对象
      // objWeakMap对象 -> obj对象 -> map对象 -> key -> dep对象  
      let map = objMap.get(obj)
      if(!map) {
        map = new Map()
        objMap.set(obj, map)
      }
    
      // 2.根据key,找到对应的depend对象
      let dep = map.get(key)
      if(!dep) {
        dep = new Depend();
        map.set(key, dep)
      }
    
      return dep
    }
    
    
    
    
    // 监听属性变化数据劫持
    // 方案一:Object.defineProperty  -> Vue2
    Object.keys(obj).forEach(key => {
      let value = obj[key];
      Object.defineProperty(obj, key, {
        set: function(newValue) {
          value = newValue;
          // 更新属性值时触发
          // 调用dep.notify()通知所有依赖函数
          const dep = getDepend(obj, key)
          dep.notify()
        },
        get: function() {
          // 拿到obj -> key
          // console.log('get函数中:', obj, key);
          // 找到对应的obj对象的key对应的dep对象
          // 自动收集依赖关系
          const dep = getDepend(obj, key)
          // 两种写法:一种需要响应式函数参数,一种直接写到类方法里面
          // dep.addDepend(reactiveFn)
          dep.depend()
          // obj.name -> dep对象 reactiveFns 
          // obj.age -> dep对象 reactiveFns
          return value;
        }
      })
    })
    
    
    
    // 方案二:new Proxy()  -> Vue3(暂时不写)
    
    
    // 设置一个专门执行响应式函数的一个函数
    let reactiveFn = null // 自由变量
    function watchFn (fn) {
      reactiveFn = fn
      fn()
      reactiveFn = null 
    }
    
    // 自动收集依赖:只要把foo函数传入watchFn()中,不管是否发生变化都添加到dep中的reactiveFns去,会存在一个问题
    // 当name发生变化时只需要通知foo函数, age发生变化时也通知foo和bar函数,修改obj.name时发现foo和bar函数都执行了,这是不对的
    
    //             name发生变化时只通知name发生变化的函数,但是obj对象对应的是同一个Depend对象,会造成对应name属性和age属性的依赖都放到一个Depend对象中,造成无法区分那个属性依赖发生变化
    // obj对象   
    //             age发生变化时只通知age发生变化的函数
    
    
    // 响应式函数
    watchFn(function foo () {
      console.log('foo function');
      console.log('foo: ', obj.name);
      console.log('foo: ', obj.age);
    
    })
    
    watchFn(function bar () {
      console.log('bar: ', obj.age + 10);
      console.log('bar function');
    })
    
    
    
    // 修改obj的属性
    console.log('age发生变化时----------------------------------------');
    // obj.name = 'kobe'
    obj.age = 18
    
    
    1. 完整数据结构图如下
  • 在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到