响应式:当响应式的数据发生更改时,若在别处有对该数据有依赖的函数则将重新执行一次。
定义一个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类的实现,存在以下几个问题:
-
- 两个函数对state对象的依赖是手动添加的
-
- 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老师讲得真的很细节!