深入浅出JS—16 Vue2和Vue3的响应式的实现

深入浅出JS—16 Vue2和Vue3的响应式的实现,第1张

本文详细介绍了响应式原理,手把手实现Vue中的响应式

目录 1 什么是响应式2 响应式函数的封装3 类的封装4 数据结构的选择5 监听对象变化Vue2—Object.definePropertyVue3—Proxy 6 代码设计

1 什么是响应式

响应式可以理解为:数据发生改变时,用到该数据的代码块自动重新执行。简而言之,就是A变,依赖A(用到A的值)的函数也跟着变

那么就需要依次考虑以下问题:

1 如何监听到A值的改变?:Proxy代理set捕获器可以监听值改变2 监听到之后,重新执行用到A相关的函数,那么如何知道哪些函数用到了A? 2.1 每个A都对应一个数组,里面存用到它的函数2.2 怎么知道函数用到A: 将普通函数封装为响应式函数封装过程中先调用一次函数,就可以在属性get *** 作中捕获该函数

2 响应式函数的封装
function foo(){
	console.log(objProxy.name);
}

let activeReactiveFn = null; // 全局变量,保存当前执行的响应函数
watchFn(fn){
	activeReactiveFn = fn;
	fn(); // 执行一次 可以在用到对象的get方法中捕获
	activeReactiveFn = null;
}

3 类的封装

每个对象的属性都对应一个数组,来保存依赖它的函数。对该数组主要进行两种 *** 作

添加元素:将新的依赖函数加入数组遍历数组:数据变化时,对数组遍历,执行每个函数

由于是围绕一个数组,进行 *** 作,所以将数组和 *** 作封装为一个Depend类来管理
由于一个函数中可能多次用到对象的属性,只要执行一次就可以,所以要求数组中元素不能重复,因此选择Set集合来实现

class Depend {
  constructor() {
    this.reactiveFn = new Set();
  }

  depend() {
    activeReactiveFn && this.addDepend(activeReactiveFn);
  }

  addDepend(fn) {
    this.reactiveFn.add(fn);
  }

  notify() {
    this.reactiveFn.forEach((fn) => fn());
  }
}
4 数据结构的选择

每个对象的每个属性都对应一个集合,可以采用WeakMap,其中key为对象,value为map对象;每个map映射表的key保存对象的属性名,value保存对应的Depend类型对象

WeakMap的好处在于它对key(obj1和obj2)是弱引用,当key对象销毁时,不会影响GC对对象的回收

5 监听对象变化 Vue2—Object.defineProperty
function reactive(obj) {
  Object.keys(obj).forEach((key) => {
    let value = obj[key];
    Object.defineProperty(obj, key, {
      configurable: true,
      enumerable: true,
      get() {
        const depend = getDepend(obj, key);
        depend.depend();
        return value;
      },
      set(newValue) {
        value = newValue;
        const depend = getDepend(obj, key);
        depend.notify();
      },
    });
  });
  return obj;
}
Vue3—Proxy
function reactive(obj) {
  return new Proxy(obj, {
    get(target, key, receiver) {
      // 收集依赖
      const depend = getDepend(target, key);
      depend.depend();
      return Reflect.get(target, key, receiver);
    },

    set(target, key, newValue, receiver) {
      Reflect.set(target, key, newValue, receiver);
      // 通知依赖
      const depend = getDepend(target, key);
      depend.notify();
    },
  });
}


6 代码设计
// 构造依赖类
class Depend {
  constructor() {
    this.reactiveFn = new Set();
  }

  depend() {
    activeReactiveFn && this.addDepend(activeReactiveFn);
  }

  addDepend(fn) {
    this.reactiveFn.add(fn);
  }

  notify() {
    this.reactiveFn.forEach((fn) => fn());
  }
}

// 查找/获取依赖
const targetMap = new WeakMap();
function getDepend(target, key) {
  let map = targetMap.get(target);
  if (!map) {
    map = new Map();
    targetMap.set(obj, map);
  }

  let depend = map.get(key);
  if (!depend) {
    depend = new Depend();
    map.set(key, depend);
  }

  return depend;
}

// 响应函数封装函数
let activeReactiveFn = null;
function watchFn(fn) {
  activeReactiveFn = fn;
  fn();
  activeReactiveFn = null;
} 

// 监听对象变化vue3(也可以替换为vue2的版本
function reactive(obj) {
  return new Proxy(obj, {
    get(target, key, receiver) {
      // 收集依赖
      const depend = getDepend(target, key);
      depend.depend();
      return Reflect.get(target, key, receiver);
    },

    set(target, key, newValue, receiver) {
      Reflect.set(target, key, newValue, receiver);
      // 通知依赖
      const depend = getDepend(target, key);
      depend.notify();
    },
  });
}

const obj = {
  name: "xs",
  age: 18,
};

function foo() {
  console.log(objProxy.name);
}

const objProxy = reactive(obj);
watchFn(foo);
// test
objProxy.name = "kobe";

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/940830.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-17
下一篇 2022-05-17

发表评论

登录后才能评论

评论列表(0条)

保存