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

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

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";
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值