vue2中使用Object.defineProperty实现
vue3中使用Proxy实现
简单原理
当访问响应式数据时
当设置响应式数据时
上述图例没有将桶与可变数据进行关联,将所有副作用函数都放入了一个桶中,会导致每个属性变化都会导致桶中函数执行
所以需要设计对应关系
函数储存桶的数据结构
这里有一个例子
const map = new Map();
const weakmap = new WeakMap();
(function(){
const foo = {foo: 1};
const bar = {bar: 2};
map.set(foo, 1);
weakmap.set(bar, 2);
})()
其中,当闭包函数执行完之后,bar变量将被回收,而foo变量则会继续保存。这是因为WeekMap区别于Map数据,WeekMap对key的持有是弱引用,所以不会影响垃圾回收机制对没有引用的数据进行回收,而foo因为作为key被map引用着,所以不会被回收。
// 存储副作用函数的桶
const bucket = new WeakMap()
const obj = new Proxy(data, {
// 拦截读取操作
get(target, key) {
// 将副作用函数 activeEffect 添加到存储副作用函数的桶中
track(target, key);
// 返回属性值
return target[key];
},
// 拦截设置操作
set(target, key, newVal) {
// 设置属性值
target[key] = newVal;
// 把副作用函数从桶里取出并执行
trigger(target, key);
}
});
// 在 get 拦截函数内调用 track 函数追踪变化
function track(target, key) {
// 没有 activeEffect,直接 return。activeEffect为当前正在执行的函数(如渲染函数)
if (!activeEffect) return;
let depsMap = bucket.get(target);
if (!depsMap) {
bucket.set(target, (depsMap = new Map()));
}
let deps = depsMap.get(key);
if (!deps) {
depsMap.set(key, (deps = new Set()));
}
deps.add(activeEffect);
}
// 在 set 拦截函数内调用 trigger 函数触发变化
function trigger(target, key) {
const depsMap = bucket.get(target);
if (!depsMap) return;
const effects = depsMap.get(key);
effects && effects.forEach(fn => fn());
}
考虑下面的代码
const data = { ok: true, text: 'hello world' }
const obj = new Proxy(data, { /* ... */ })
effect(function effectFn() {
document.body.innerText = obj.ok ? obj.text : 'not'
})