Vue响应式原理

323

响应式:当响应式的数据发生更改时,若在别处有对该数据有依赖的函数则将重新执行一次。

定义一个Dep类用于收集依赖

class Dep {
  constructor() {
    // 收集依赖一般使用集合(集合里面的元素是不重复的)
    this.subscribers = new Set();
  }
  addEffect(effect) {
    this.subscribers.add(effect);
  }
  notify() {
    this.subscribers.forEach((effect) => effect());
  }
}

const dep = new Dep();
const state = { name: "coderwhh", age: 12 };

function sayName() {
  console.log(state.name);
}
function sayHello() {
  console.log("Hello!" + state.name);
}

// 手动绑定依赖
dep.addEffect(sayName);
dep.addEffect(sayHello);

state.name = "猪八戒";
// 数据发生更改,手动通知
dep.notify();

上面的便是一个简单的Dep类的实现,存在以下几个问题:

    1. 两个函数对state对象的依赖是手动添加的
    1. state对象发生更改,通知也是依赖函数的重新执行也是手动通知的

带着上面存在的两个问题我们可以再完善以下这个Dep类

class Dep {
  constructor() {
    // 收集依赖一般使用集合(集合里面的元素是不重复的)
    this.subscribers = new Set();
  }
  depend() {
    if (activeEffect) {
      this.subscribers.add(activeEffect);
    }
  }
  notify() {
    this.subscribers.forEach((effect) => effect());
  }
}

let activeEffect = null;
function watchEffect(effect) {
  activeEffect = effect;
  dep.depend();
  // 传入默认函数时首先执行一次
  effect();
  activeEffect = null;
}

const dep = new Dep();
const state = { name: "coderwhh", age: 12 };
const state2 = { name: "李银河", age: 18 };

watchEffect(function () {
  console.log(state.name);
});
watchEffect(function () {
  console.log("Hello!" + state.name);
});
watchEffect(function () {
  console.log(state.age);
});
watchEffect(function () {
  console.log(state2.name);
});

state.name = "猪八戒";
dep.notify();

以上对Dep类进行了部分的完善: 使用watchEffect函数,达到了自动收集依赖的目的。 但是仔细的同学不难发现,以上的Dep类在监听到任何一个数据的改变时,会将所有的副作用函数都执行一次,这显然不是我们想达到的目的,所以咋们可以带着这个问题进行再一次的完善,应该根据函数里面依赖的数据创建多个dep,来收集其对应的依赖。

eg:dep1(state.name) subscribers; dep2(state.age) subscribers; dep3(state2.nanme) subscribers

class Dep {
  constructor() {
    // 收集依赖一般使用集合(集合里面的元素是不重复的)
    this.subscribers = new Set();
  }
  depend() {
    if (activeEffect) {
      this.subscribers.add(activeEffect);
    }
  }
  notify() {
    this.subscribers.forEach((effect) => effect());
  }
}

let activeEffect = null;
function watchEffect(effect) {
  activeEffect = effect;
  // 传入默认函数时首先执行一次
  effect();
  activeEffect = null;
}

/**
 * Map{key: value} => key是一个字符串
 * WeakMap{key: value} => key是一个对象,弱引用
 * 使用WeakMap时若将key = null, 则可以将其绑定的事件进行垃圾回收
 * **/
const targetMap = new WeakMap();
function getDep(target, key) {
  // 根据(target)对象取出对应的Map对象
  let depsMap = targetMap.get(target);
  if (!depsMap) {
    depsMap = new Map();
    targetMap.set(target, depsMap);
  }
  // 取出具体的dep对象
  let dep = depsMap.get(key);
  if (!dep) {
    dep = new Dep();
    depsMap.set(key, dep);
  }
  return dep;
}

// 对raw进行数据劫持 => vue2
function reactive(raw) {
  Object.keys(raw).forEach((key) => {
    const dep = getDep(raw, key);
    let value = raw[key];
    Object.defineProperty(raw, key, {
      get() {
        // 自动收集依赖
        dep.depend();
        return value;
      },
      set(newValue) {
        if (value !== newValue) {
          value = newValue;
          // 自动通知依赖
          dep.notify();
        }
      },
    });
  });
  return raw;
}

const state = reactive({ name: "coderwhh", age: 12 });
const state2 = reactive({ name: "李银河", age: 18 });

watchEffect(function () {
  console.log(state.name);
});
watchEffect(function () {
  console.log("Hello!" + state.name);
});
watchEffect(function () {
  console.log(state.age);
});
watchEffect(function () {
  console.log(state2.name);
});

state.name = "猪八戒";
setTimeout(() => {
  state2.name = "孙悟空";
}, 2000);

以上便是一个简单的vue2响应式原理的实现.为什么vue3会选择使用Proxy呢?

  • 1.Object.definedProperty是劫持对象的属性时,如果新增元素,则vue2需要再次调用definedProperty,然而Proxy是劫持整个对象,不需要做特殊处理

  • 2.修改的对象不同时,使用definedProperty时,我们修改原来的obj对象就可以触发拦截,而使用Proxy就必须得修改代理对象,即Proxy的实例才可以触发拦截

  • 3.Proxy能观察的类型比definedProperty更加的丰富

    • has: in操作符的捕获器
    • deleteProperty: delete操作符的捕捉器

vue3中reactive的实现方式

class Dep {
  constructor() {
    // 收集依赖一般使用集合(集合里面的元素是不重复的)
    this.subscribers = new Set();
  }
  depend() {
    if (activeEffect) {
      this.subscribers.add(activeEffect);
    }
  }
  notify() {
    this.subscribers.forEach((effect) => effect());
  }
}

let activeEffect = null;
function watchEffect(effect) {
  activeEffect = effect;
  // 传入默认函数时首先执行一次
  effect();
  activeEffect = null;
}

/**
 * Map{key: value} => key是一个字符串
 * WeakMap{key: value} => key是一个对象,弱引用
 * 使用WeakMap时若将key = null, 则可以将其绑定的事件进行垃圾回收
 * **/
const targetMap = new WeakMap();
function getDep(target, key) {
  // 根据(target)对象取出对应的Map对象
  let depsMap = targetMap.get(target);
  if (!depsMap) {
    depsMap = new Map();
    targetMap.set(target, depsMap);
  }
  // 取出具体的dep对象
  let dep = depsMap.get(key);
  if (!dep) {
    dep = new Dep();
    depsMap.set(key, dep);
  }
  return dep;
}

// 对raw进行数据劫持 => vue3
function reactive(raw) {
  return new Proxy(raw, {
    get(target, key) {
      const dep = getDep(target, key);
      dep.depend();
      return target[key];
    },
    set(target, key, newValue) {
      const dep = getDep(target, key);
      dep.notify();
      target[key] = newValue;
    },
  });
}

const state = reactive({ name: "coderwhh", age: 12 });
const state2 = reactive({ name: "李银河", age: 18 });

watchEffect(function () {
  console.log(state.name);
});
watchEffect(function () {
  console.log("Hello!" + state.name);
});
watchEffect(function () {
  console.log(state.age);
});
watchEffect(function () {
  console.log(state2.name);
});

state.name = "猪八戒";
setTimeout(() => {
  state2.name = "孙悟空";
}, 2000);

写在最后:此部分为学习coderwhy的vue3的读书笔记,coderwhy老师讲得真的很细节!