likes
comments
collection
share

vue2 - vue3响应式原理代码的实现(二)

作者站长头像
站长
· 阅读数 49

一、对象的依赖管理

  • 目前是创建了一个Depend对象,用来管理对于name变化需要监听的响应函数;
  • 通过WeakMap来管理不同对象的不同依赖关系;

1.1. 对象依赖管理的实现

  • 监听对象的变化代码重构
    const targetMap = new WeakMap();
    
    const objProxy = new Proxy(obj, {
      get(target, key, receiver) {
        return Reflect.get(target, key, receiver);
      },
    
      set(target, key, newVal, receiver) {
        Reflect.set(target, key, newVal, receiver);
    
        let map = targetMap.get(target);
        if (!map) {
          map = new Map();
          targetMap.set(target, map);
        };
    
        let depend = map.get(key);
        if (!depend) {
          depend = new Depend();
          map.set(key, depend);
        };
    
        depend.notify();
      }
    });
    

1.2. 正确的依赖收集

  • 收集依赖watchFn代码重构;
  • 如果一个函数中使用了某个对象的key,那么它应该被收集依赖;
    //原来:
    //function watchFn(fn) {
    //  depend.addDepend(fn);
    //};
    
    //更改为:
    let activefn = null;
    function watchFn(fn) {
      activefn = fn;
      //7.调用函数会触发get
      fn();
      activefn = null;
    };
    
    // 封装一个获取depend的函数
    const targetMap = new WeakMap();
    function getDepend(target, key) {
      let map = targetMap.get(target);
      if (!map) {
        map = new Map();
        targetMap.set(target, map);
      };
    
      let depend = map.get(key);
      if (!depend) {
        depend = new Depend();
        map.set(key, depend);
      };
    
      return depend;
    };
    
    const objProxy = new Proxy(obj, {
      get(target, key, receiver) {
        const depend = getDepend(target, key);
        depend.addDepend(activefn);
    
        return Reflect.get(target, key, receiver);
      },
      set(target, key, newVal, receiver) {
        Reflect.set(target, key, newVal, receiver);
    
        const depend = getDepend(target, key);
        depend.notify();
      }
    });
    
    watchFn(function() {
      console.log(objProxy.name, 'name需要响应的代码块');
    });
    
    watchFn(function() {
      console.log(objProxy.age, 'age需要响应的代码块');
    });
    
    objProxy.name = 'ace';
    /*
    yzh name需要响应的代码块
    18 age需要响应的代码块
    ace name需要响应的代码块
    */
    

二、Depend重构

  • 问题一:如果函数中有用到两次key,比如name,那么这个函数会被收集两次;
    • 解决:不使用数组,而是使用Set;
  • 问题二:我们并不希望将添加reactiveFn放到get中,因为它是属于Dep的行为;
    • 解决:addDepend方法优化;
    let activefn = null;
    
    class Depend {
      constructor() {
        // this.reactiveFns = [];
        this.reactiveFns = new Set();
      }
    
      // addDepend(reactiveFn) {
      //   this.reactiveFns.push(reactiveFn)
      // }
      addDepend() {
        if (activefn) {
          this.reactiveFns.add(activefn);
        }
      }
    
      notify() {
        this.reactiveFns.forEach(fn => {
          fn();
        })
      }
    }
    
    const objProxy = new Proxy(obj, {
      get(target, key, receiver) {
        const depend = getDepend(target, key);
        // depend.addDepend(activefn);
        depend.addDepend();
    
        return Reflect.get(target, key, receiver);
      },
      set(target, key, newVal, receiver) {
        Reflect.set(target, key, newVal, receiver);
    
        const depend = getDepend(target, key);
        depend.notify();
      }
    })
    

三、创建响应式对象

  • 目前的响应式是针对于obj一个对象的,我们可以创建出来一个函数,针对所有的对象都可以变成响应式对象;
  • 完整代码:
    let activefn = null;
    
    // 3.
    class Depend {
      constructor() {
        this.reactiveFns = new Set();
      }
    
      addDepend() {
        if (activefn) {
          this.reactiveFns.add(activefn);
        }
      }
    
      notify() {
        this.reactiveFns.forEach(fn => {
          fn();
        })
      }
    };
    
    // 1.
    function watchFn(fn) {
      activefn = fn;
      //7.调用函数会触发get
      fn();
      activefn = null;
    };
    
    // 5.封装一个获取depend函数
    const targetMap = new WeakMap();
    function getDepend(target, key) {
      // 7.1.根据target对象获取map,第一次肯定是没有所以设置进去
      let map = targetMap.get(target);
      if (!map) {
        map = new Map();
        targetMap.set(target, map);
      };
    
      // 根据key获取depend对象,第一次肯定是没有所以设置进去
      let depend = map.get(key);
      if (!depend) {
        depend = new Depend();
        map.set(key, depend);
      };
    
      return depend;
    };
    
    function reactive(obj) {
      return new Proxy(obj, {
        get(target, key, receiver) {
          //根据target key获取对应的depend
          const depend = getDepend(target, key);
          depend.addDepend();
    
          return Reflect.get(target, key, receiver);
        },
        set(target, key, newVal, receiver) {
          Reflect.set(target, key, newVal, receiver);
    
          // 6.拿到属于变化的depend调用notify
          const depend = getDepend(target, key);
          depend.notify();
        }
      });
    };
    
    const info = reactive({
      name: 'luffy'
    });
    
    watchFn(() => {
      console.log(info.name);
    });
    
    info.name = 'yzh';
    

四、Vue2响应式原理

  • 可以将reactive函数进行如下的重构:
    • 在传入对象时,我们可以遍历所有的key,并且通过属性存储描述符来监听属性的获取和修改;
    • 在setter和getter方法中的逻辑和前面的Proxy是一致的;
      function reactive(obj) {
        Object.keys(obj).forEach(key => {
          let value = obj[key];
          Object.defineProperty(obj, key, {
            get() {
              const depend = getDepend(obj, key);
              depend.addDepend();
      
              return value;
            },
      
            set(newVal) {
              value = newVal;
              const depend = getDepend(obj, key);
      
              depend.notify();
            }
          })
        });
      
        return obj;
      };
      
转载自:https://juejin.cn/post/7182747796892549178
评论
请登录