【Vue】Vue2-Vue3响应式原理

什么是响应式?

我们先来看一下响应式意味着什么?
‍♀️ 我们来看一段代码:⬇️

  • m有一个初始化的值,有一段代码使用了这个值;
  • 那么在m有一个新的值时,这段代码可以自动重新执行;
let m = 20;
console.log(m);
console.log(m*2);


m=40;

⬆️ 上面的这样一种可以自动响应数据变量的代码机制,我们称之为是响应式的。
对象的响应式:⬇️
【Vue】Vue2-Vue3响应式原理_第1张图片

响应式函数设计

☝️ 首先,执行的代码中可能不止一行代码,所以我们可以将这些代码放到一个函数中:

  • 那么我们的问题就变成了,当数据发生变化时,自动去执行某一个函数;

【Vue】Vue2-Vue3响应式原理_第2张图片
但是,有一个问题:
❓❓在开发中我们是有很多的函数的,我们如何区分一个函数需要响应式,还是不需要响应式呢?

  • 很明显,下面的函数中 foo 需要在obj的name发生变化时,重新执行,做出相应;
  • bar函数是一个完全独立于obj的函数,它不需要执行任何响应式的操作;
function foo() {
  //依赖于obj.name
  let newName = obj.name;
  console.log(obj.name);
}
function bar() {
  const result = 20 + 20;
  console.log(result);
  console.log('hello world');
}

响应式函数的实现watchFn

将依赖于另一个数据的代码片段,放进一个函数中,
当数据发生变化时,依赖于该数据的函数自动执行。
但是,我们怎么区分呢?

  • 这个时候我们封装一个新的函数watchFn;
  • 凡是传入到watchFn的函数,就是需要响应式的;
  • 其他默认定义的函数都是不需要响应式的;
let obj = {
  name: '张三',
  age: 32,
};

const reactiveFns = [];

function watchFn(fn) {
  reactiveFns.push(fn);
  fn();
}

reactiveFns.forEach((fn) => {
  fn();
});

console.log('---name发现变化----');
obj.name = '李四';
watchFn(function () {
  let newName = obj.name;
  console.log('foo:', obj.name);
  console.log('bar:', obj.age);
});
watchFn(function () {
  console.log('bar:', obj.age);
  console.log('bar:', obj.name + '!!');
});

响应式依赖的收集

  • 目前我们收集的依赖是放到一个数组中来保存的,
  • ❓ 但是这里会存在数据管理的问题:
    • 我们在实际开发中需要监听很多对象的响应式;
    • 这些对象需要监听的不只是一个属性,它们很多属性的变化,都会有对应的响应式函数;
    • 我们不可能在全局维护一大堆的数组来保存这些响应函数;
  • 所以我们要设计一个类,这个类用于管理某一个对象的某一个属性的所有响应式函数: 
    • 相当于替代了原来的简单 reactiveFns 的数组;
class Depend {
  constructor() {
    this.reactiveFns = [];
  }
  addDepend(fn) {
    if (fn) {
      this.reactiveFns.push(fn);
    }
  }
  notify() { //通知函数重新执行
    this.reactiveFns.forEach((fn) => {
      fn();
    });
  }
}
let obj = {
  name: '张三',
  age: 32,
};
// 设置一个专门执行响应式函数的一个函数
const dep = new Depend();
function watchFn(fn) {
  dep.addDepend(fn);
  fn();
}


// console.log('---name发现变化----');


watchFn(function foo() {
  let newName = obj.name;
  console.log('foo:', obj.name);
  console.log('foo:', obj.age);
  console.log('foo:function');
});
watchFn(function bar() {
  console.log('bar:', obj.age + 20);
  console.log('bar:', obj.name + '!!');
  console.log('bar:function');
});


console.log('---修改obj的属性值---');
console.log('-----name发生改变-----');


obj.name = '李四';
dep.notify(); //每次都是手动触发 修改通知


console.log('-----age发生改变-----');
obj.age = 42;
dep.notify();


console.log('-----name发生改变-----');


obj.name = '王五';
// 不通知dep.notify(); 就不会执行 

监听对象的变化

那么我们接下来就可以通过之前学习的方式来监听对象的变量: 
方式一:通过 Object.defineProperty 的方式(vue2采用的方式); 
方式二:通过new Proxy的方式(vue3采用的方式);

// 方式一:Object.defineProperty
Object.keys(obj).forEach((key) => {
  let value = obj[key];
  Object.defineProperty(obj, key, {
    set: function (newValue) {
      value = newValue;
      dep.notify();
    },
    get: function () {
      return value;
    },
  });
});
  const objProxy = new Proxy(obj, {
    set: function (target, key, newValue, receiver) {
      Reflect.set(target, key, newValue, receiver);
      dep.notify();
    },
    get: function (target, key, receiver) {
      return Reflect.get(target, key, receiver);
    },
  });

❓❓此时的问题:

改变一个对象的属性值,整个函数都会重新执行,实际上我们需要执行的只是依赖于发生改变的属性的函数。
比如:改变obj的name属性,只需要通知只是依赖于name的函数重新执行
但是目前的问题:
对obj来说,对应的是同一个dep对象;
只有一个dep对象,对于name属性依赖的函数和age属性依赖的函数都在同一个dep对象的属性reactiveFns中;
属性的依赖都在reactiveFns数组中,无法区分。
【Vue】Vue2-Vue3响应式原理_第3张图片
解决方式:⬇️ 通过 map

【Vue】Vue2-Vue3响应式原理_第4张图片

对象的依赖管理

我们目前是创建了一个Depend对象,用来管理对于name变化需要监听的响应函数: 
但是实际开发中我们会有不同的对象,另外会有不同的属性需要管理;
我们如何可以使用一种数据结构来管理不同对象的不同依赖关系呢?
在前面我们刚刚学习过WeakMap,并且在学习WeakMap的时候我讲到了后面通过WeakMap如何管理这种响应式的数据依赖:
【Vue】Vue2-Vue3响应式原理_第5张图片

对象依赖管理的实现

分析:比如:obj.name 使用就放进 obj的map中的name对应的dep中

  1. dep对象数据结构的管理(最难理解)
    1. 每一个对象的每一个属性都会对应一个dep对象
    2. 每一个对象的多个属性的dep对象是存放在一个map对象中
    3. 多个对象的map对象,会被存放到一个objMap的对象中
  2. 依赖收集:当执行get函数,自动添加fn函数

写一个getDepends()专门管理

// 自动通过obj的key自动收集对象的Depend对象
const objMap = new WeakMap();
function getDepends(obj, key) {
  //1.根据对象的obj,找到对应的Depend对象
  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;
}

正确的依赖收集

我们之前收集依赖的地方是在 watchFn 中:
但是这种收集依赖的方式我们根本不知道是哪一个key的哪一个depend需要收集依赖;
你只能针对一个单独的depend对象来添加你的依赖对象;
那么正确的应该是在哪里收集呢?
应该在我们调用了Proxy的get捕获器时
因为如果一个函数中使用了某个对象的key,那么它应该被收集依赖;

// 设置一个专门执行响应式函数的一个函数
let reactiveFn = null;
function watchFn(fn) {
  reactiveFn = fn;
  fn();
  reactiveFn = null;
}
// 方式一:Object.defineProperty
Object.keys(obj).forEach((key) => {
  let value = obj[key];
  Object.defineProperty(obj, key, {
    set: function (newValue) {
      value = newValue;
      const dep = getDepends(obj, key);
      dep.notify();
    },
    get: function () {
      // console.log(obj, key);
      // 精准的找到obj key的dep对象
      const dep = getDepends(obj, key);
      dep.addDepend(reactiveFn);
      return value;
    },
  });
});
//方式二:通过new Proxy的方式(vue3采用的方式); 
const objProxy = new Proxy(obj, {
  set: function (target, key, newValue, receiver) {
    Reflect.set(target, key, newValue, receiver);
    const dep = getDepends(target, key);
    dep.notify();
  },
  get: function (target, key, receiver) {
    const dep = getDepends(target, key);
    dep.addDepend(reactiveFn);
    return Reflect.get(target, key, receiver);
  },
});

对Depend重构

⚠️ 但是这里有两个问题:
❓❓问题一:如果函数中有用到两次key,比如name,那么这个函数会被收集两次;
❓❓问题二:我们并不希望将添加reactiveFn放到get中,以为它是属于Dep的行为;
所以我们需要对Depend类进行重构:
 ✅ ✅ 解决问题一的方法:不使用数组,而是使用Set;
 ✅ ✅ 解决问题二的方法:添加一个新的方法,用于收集依赖;

let obj = {
  name: '张三',
  age: 32,
  address: '北京',
};
Object.keys(obj).forEach((key) ...
......

watchFn(function () {
  console.log('----1----');
  console.log(obj.name);
  console.log(obj.age);
});


watchFn(function () {
  console.log('----2----');
  console.log(obj.age); 
  console.log(obj.age);
});


watchFn(function () {
  console.log('----3----');
  console.log(obj.name);
  console.log(obj.address);
});


// 改变age
obj.age = 26; 

【Vue】Vue2-Vue3响应式原理_第6张图片

问题: console.log(obj.age);执行了两次
解决:⬇️

class Depend {
  constructor() {
    this.reactiveFns = new Set(); //Set 中的元素只会出现一次
  }
  addDepend(fn) {
    if (fn) {
      // this.reactiveFns.push(fn);
      this.reactiveFns.add(fn);
    }
  }
  depend() {
    if (reactiveFn) {
      this.reactiveFns.add(reactiveFn);
    }
  }
  notify() {
    this.reactiveFns.forEach((fn) => {
      fn();
    });
  }
}

多个对象响应式

封装Object

function reactive(obj) {
  //  方式一:Object.defineProperty
  Object.keys(obj).forEach((key) => {
    let value = obj[key];
    Object.defineProperty(obj, key, {
      set: function (newValue) {
        value = newValue;
        const dep = getDepends(obj, key);
        dep.notify();
      },
      get: function () {
        // console.log(obj, key);
        // 精准的找到obj key的dep对象
        const dep = getDepends(obj, key);
        dep.addDepend(reactiveFn);


        return value;
      },
    });
  });

  return obj;
}
// 方式二: new proxy()
function reactive(obj) {
  const objProxy = new Proxy(obj, {
    set: function (target, key, newValue, receiver) {
      Reflect.set(target, key, newValue, receiver);
      const dep = getDepends(target, key);
      dep.notify();
    },
    get: function (target, key, receiver) {
      const dep = getDepends(target, key);
      dep.addDepend(reactiveFn);
      return Reflect.get(target, key, receiver);
    },
  });

  return objProxy;
}
// ----业务代码----
let obj1 = reactive({
  name: '张三',
  age: 32,
  address: '北京',
});


watchFn(function () {
  console.log('----obj1----');
  console.log(obj1.name);
  console.log(obj1.age);
  console.log(obj1.address);
});


const user = reactive({
  nikiname: 'park',
  level: 11,
});


watchFn(function () {
  console.log('----user----');
  console.log(user.nikiname);
  console.log(user.level);
});

完整代码:

class Depend {
  constructor() {
    this.reactiveFns = new Set();
  }
  addDepend(fn) {
    if (fn) {
      // this.reactiveFns.push(fn);
      this.reactiveFns.add(fn);
    }
  }
  depend() {
    if (reactiveFn) {
      this.reactiveFns.add(reactiveFn);
    }
  }
  notify() {
    this.reactiveFns.forEach((fn) => {
      fn();
    });
  }
}


// 设置一个专门执行响应式函数的一个函数
let reactiveFn = null;
function watchFn(fn) {
  reactiveFn = fn;
  fn();
  reactiveFn = null;
}
// 自动通过obj的key自动收集对象的Depend对象
const objMap = new WeakMap();
function getDepends(obj, key) {
  //1.根据对象的obj,找到对应的Depend对象
  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
function reactive(obj) {
  Object.keys(obj).forEach((key) => {
    let value = obj[key];
    Object.defineProperty(obj, key, {
      set: function (newValue) {
        value = newValue;
        const dep = getDepends(obj, key);
        dep.notify();
      },
      get: function () {
        // console.log(obj, key);
        // 精准的找到obj key的dep对象
        const dep = getDepends(obj, key);
        //dep.addDepend(reactiveFn);
        dep.depend();
        return value;
      },
    });
  });


  return obj;
}
// // 方式二: new proxy()
// function reactive(obj) {
//   const objProxy = new Proxy(obj, {
//     set: function (target, key, newValue, receiver) {
//       Reflect.set(target, key, newValue, receiver);
//       const dep = getDepends(target, key);
//       dep.notify();
//     },
//     get: function (target, key, receiver) {
//       const dep = getDepends(target, key);
//       dep.depend();
//       return Reflect.get(target, key, receiver);
//     },
//   });
//
//   return objProxy;
// }
// ----业务代码----
let obj1 = reactive({
  name: '张三',
  age: 32,
  address: '北京',
});


watchFn(function () {
  console.log('----obj1----');
  console.log(obj1.name);
  console.log(obj1.age);
  console.log(obj1.address);
});


const user = reactive({
  nikiname: 'park',
  level: 11,
});


watchFn(function () {
  console.log('----user----');
  console.log(user.nikiname);
  console.log(user.level);
});


// 改变


user.level = 12;

你可能感兴趣的:(Vue,笔记,前端,vue.js,javascript,前端)