Vue3源码学习02-实现响应式系统
// // 假如现在有个counter
// let counter = 100;
// // 这些是对counter进行操作的函数,对counter有依赖
// function doubleCounter() {
// console.log(counter * 2);
// }
// function powerCounter() {
// console.log(counter * counter);
// }
// // 当counter的值发生变化,这些依赖于counter的函数如何通知它们重新执行呢?
// counter++;
// 最基本的依赖收集系统
class Dep {
constructor() {
this.subscriber = new Set();
}
// 收集这些依赖于数据操作的函数
addEffect(fn) {
this.subscriber.add(fn);
}
// 当数据发生改变时,通知这些函数重新执行
notify() {
this.subscriber.forEach((fn) => {
fn();
});
}
}
let counter = 100;
function doubleCounter() {
console.log(counter * 2);
}
function powerCounter() {
console.log(counter * counter);
}
const dep = new Dep();
dep.addEffect(doubleCounter);
dep.addEffect(powerCounter);
counter++; // 数据发生改变
dep.notify()
class Dep {
constructor() {
this.subscriber = new Set();
}
depend() {
if (activeEffect) {
this.subscriber.add(activeEffect);
}
}
notify() {
this.subscriber.forEach((effect) => {
effect();
});
}
}
let counter = 100;
const dep = new Dep();
// 把依赖于counter的函数做一层封装,在这个函数里执行
// 这是一个标记,把当前执行的函数保存下来,在收集的时候就看这个标记保存的是哪个函数
let activeEffect = null;
function watchEffect(effect) {
// 当前执行的函数,先添加到依赖收集系统中
activeEffect = effect;
dep.depend();
// 随后执行这个函数
effect();
activeEffect = null;
}
watchEffect(function () {
console.log(counter * 2);
});
watchEffect(function () {
console.log(counter * counter);
});
counter++;
dep.notify()
class Dep {
constructor() {
this.subscriber = new Set();
}
depend() {
if (activeEffect) {
this.subscriber.add(activeEffect);
}
}
notify() {
this.subscriber.forEach((effect) => {
effect();
});
}
}
// Map: ({key:value}) key是一个字符串
// WeakMap: ({key(对象):value}) key是一个对象 弱引用
// 这里需要给未处理过的数据做一个Map数据结构的保存,原因是多个数据需要建立多个dep依赖收集对象
// 而每个dep需要有相应的key值做一一对应
const targetMap = new WeakMap();
// 根据不同的数据和不同的key获取不同的dep对象
function getDep(target, key) {
// 1.根据target对象取出对应的Map对象
let depsMap = targetMap.get(target);
if (!depsMap) {
depsMap = new Map();
targetMap.set(target, depsMap);
}
// 2.取出具体的dep对象
let dep = depsMap.get(key);
if (!dep) {
dep = new Dep();
}
return dep;
}
// 数据劫持:劫持该数据,当该数据发生变化时,会通知依赖数据发生了改变
// vue2的数据劫持:Object.defineProperty()
function reactive(raw) {
// 1.拿到所有的key
Object.keys(raw).forEach((key) => {
// 2.拿到这个数据对应的依赖收集对象
const dep = getDep(raw, key);
// 3.进行数据劫持
let value = raw[key];
// 参数1:进行劫持的对象
// 参数2:劫持对象的哪个key值
// 参数3:劫持的操作
Object.defineProperty(raw, key, {
get: () => {
// 只要某些地方需要获取这个数据,就触发收集依赖,收集到与这个数据相关的函数
dep.depend();
// 这里需要return value而不是return raw[key]的原因:
// 这里是直接对raw进行了数据劫持,通过raw[key]获取value会触发get函数,导致无限循环
return value;
},
set: (newValue) => {
// 只要这个数据发生了修改,就通知这些依赖重新执行
if (value !== newValue) {
value = newValue;
dep.notify();
}
},
});
});
// 返回的是已经被劫持后的数据
return raw;
}
let activeEffect = null;
function watchEffect(effect) {
// 把当前依赖于这个数据的函数赋值给一个标记,便于dep对象调用depend收集依赖
activeEffect = effect;
effect();
activeEffect = null;
}
// 测试代码
const info = reactive({ name: "jzh", age: 25, counter: 100 });
const foo = reactive({ height: 1.7 });
// 当数据发生修改时,我需要知道数据发生了修改,这点如何做到呢?通过数据劫持
// info.name = "coder";
// 每次调用watchEffect都会传入与该数据相关的函数,这个函数会被赋值给一个标记,用于dep.depend收集依赖
watchEffect(function () {
console.log(info.counter * 2, info.name);
});
watchEffect(function () {
console.log(info.counter * info.counter, info.age);
});
watchEffect(function () {
console.log(foo.height);
});
// 当数据发生修改
info.name = "coder";
foo.height = 1.75;
Vue3的响应式实现:
class Dep {
constructor() {
this.subscriber = new Set();
}
depend() {
if (activeEffect) {
this.subscriber.add(activeEffect);
}
}
notify() {
this.subscriber.forEach((effect) => {
effect();
});
}
}
const targetMap = new WeakMap();
function getDep(target, key) {
let depsMap = targetMap.get(target);
if (!depsMap) {
depsMap = new Map();
targetMap.set(target, depsMap);
}
let dep = depsMap.get(key);
if (!dep) {
dep = new Dep();
depsMap.set(key, dep);
}
return dep;
}
// vue3的数据劫持:Proxy代理对象
function reactive(raw) {
// 在代理对象中做数据劫持
return new Proxy(raw, {
get(raw, key) {
// 拿到这个数据的依赖收集对象,在其他地方获取这个数据的时候就进行收集依赖
const dep = getDep(raw, key);
dep.depend();
// 返回数据
// 这里可以操作raw[key]的原因是这是对代理对象进行了数据劫持,不是直接对raw进行了数据劫持,
// 所以通过raw[key]拿到value不会触发get函数
return raw[key];
},
set(raw, key, newValue) {
// 拿到这个数据的依赖收集对象,在其他地方修改这个数据的时候通知依赖收集对象重新执行这些依赖
if (raw[key] !== newValue) {
const dep = getDep(raw, key);
raw[key] = newValue;
dep.notify();
}
},
});
}
let activeEffect = null;
function watchEffect(effect) {
activeEffect = effect;
effect();
activeEffect = null;
}
const info = reactive({ name: "jzh", age: 25, counter: 100 });
const foo = reactive({ height: 1.7 });
watchEffect(function () {
console.log(info.counter * 2, info.name);
});
watchEffect(function () {
console.log(info.counter * info.counter, info.age);
});
watchEffect(function () {
console.log(foo.height);
});
// 当数据发生修改:
foo.height = 1.8;
info.name = 'coder'
Vue2和Vue3实现响应式的不同之处:
-
Object.definedProperty 是劫持对象的属性,如果新增属性:
-
Vue2此时需要再次调用definedProperty,对这个属性进行劫持;而Proxy劫持的是整个对象,新增的属性会自动做劫持处理
-
-
修改对象的不同:
-
使用 definedProperty 时,我们修改原来的obj对象即可触发set拦截
-
而使用Proxy,就必须修改代理对象,即Proxy实例才可触发set拦截
-

浙公网安备 33010602011771号