1. 响应式数据与副作用函数
const obj = {
text: "hello world",
};
function effect() {
document.body.innerText = obj.text;
}
effect();
obj.text = "hello vue";
期望:当改变obj.text的时候,再次执行副作用函数effect
2. 响应式数据的基本实现
观察分析:
- 当执行
effect函数时,会触发obj.text的get操作 - 当修改
obj.text的值时,会触发obj.text的set操作
思考:如何实现obj.text的set操作时候再次执行effect
如何拦截obj.txt的set操作,然后触发effect?
所以,关键的地方在于对象的读取和设置操作拦截
- 在Vue2中,使用的是Object.defineProperty()
- 在Vue3中,使用的是Proxy
通过Proxy拦截set操作,触发effect
const obj = {
text: "hello world",
};
const newObj = new Proxy(obj, {
// 在set操作中,赋值,然后调用effect函数
set(target, key, value) {
target[key] = value;
effect();
},
});
function effect() {
document.body.innerText = newObj.text;
}
effect();
newObj.text = "hello Vue3";
//页面数据发生变化:hello world --> hello Vue3
3. 设计一个完善的相应系统
如果副作用函数名称不叫作effect,如何动态收集?
封装一个收集副作用的函数,收集的同时也调用副作用函数
let activeFn = undefined;
// 在调用effect时候,将参数fn赋值给activeFn,
// 在set函数中可以直接调用activeFn,而不用关心传入函数的具体的名字
function effect(fn) {
activeFn = fn;
fn();
}
const obj = {
text: "hello world",
};
const newObj = new Proxy(obj, {
// 在set操作中,赋值,然后调用effect函数
set(target, key, value) {
target[key] = value;
activeFn();
return true;
},
});
function effect0() {
console.log("effect0", "----", newObj.text);
}
effect(effect0);
newObj.text = "hello Vue3";
如果有多个副作用函数,那么需要将副作用函数收集,在触发的时候,依次调用。
使用Set数据结构可以去重
注意:这样看似乎可以在effect注册副作用函数时候收集,也可以在对象的get操作中进行收集
let activeFn = undefined;
const bucket = new Set();
function effect(fn) {
activeFn = fn;
fn();
}
const obj = {
text: "hello world",
};
const newObj = new Proxy(obj, {
get(target, key){
// 将副作用函数存储到set中
if(activeFn){
bucket.add(activeFn)
}
return target[key]
},
// 在set操作中,赋值,然后调用effect函数
set(target, key, value) {
target[key] = value;
bucket.forEach((fn) => {
fn();
});
return true;
},
});
function effect0() {
console.log("effect0", "----", newObj.text);
}
effect(effect0);
newObj.text = "hello Vue3";
如果有两个响应式数据,那么该如何在修改某个响应式数据的时候触发对应的副作用函数?
使用Map数据结构,以原始对象为key,副作用函数为value进行存储
进阶:使用WeakMap数据结构,当原始对象没有引用时,则在WeakMap清除这个原始对象,不阻碍垃圾回收。
如果一个响应式数据中有多个属性,那么该如何在修改某个属性的时候触发对应的副作用函数?
使用Map数据结构,以原始对象的key为key,副作用函数为value进行存储
所以必须在get操作中收集副作用函数!
上面两种情况合在一起,绘制下面这幅图,清楚表示副作用函数的存储
let activeFn = undefined;
const bucket = new WeakMap();
const obj = {
text: "hello world",
};
function effect(fn) {
activeFn = fn;
fn();
}
const newObj = new Proxy(obj, {
get(target, key) {
// 如果没有注册的副作用函数,直接返回值
if (!activeFn) return target[key];
// 取出原始对象对应的副作用函数(对象所有的key对应的所有的副作用函数的集合)
let depMap = bucket.get(target);
// 如果没有原始对象对应的map则创建并于整个数据结构进行关联
if (!depMap) {
depMap = new Map();
bucket.set(target, depMap);
}
// 取出对象key对应的副作用函数的集合
let deps = depMap.get(key);
if (!deps) {
deps = new Set();
depMap.set(key, deps);
}
deps.add(activeFn);
return target[key];
},
// 在set操作中,赋值,然后调用effect函数
set(target, key, value) {
target[key] = value;
const depMap = bucket.get(target);
if (!depMap) return true;
const effects = depMap.get(key);
if (!effects) return true;
effects.forEach((fn) => {
fn();
});
return true;
},
});
function effect0() {
console.log("effect0", "----", newObj.text);
}
effect(effect0);
newObj.text = "hello Vue3";
代码优化:
- 将在
get中收集依赖的部分封装成一个函数,track - 将在
set中触发依赖的部分封装成一个函数,trigger
let activeFn = undefined;
const bucket = new WeakMap();
const obj = {
text: "hello world",
};
function effect(fn) {
activeFn = fn;
fn();
}
// 在get中收集依赖
function track(target, key) {
// 如果没有注册的副作用函数,直接返回值
if (!activeFn) return target[key];
// 取出原始对象对应的副作用函数(对象所有的key对应的所有的副作用函数的集合)
let depMap = bucket.get(target);
// 如果没有原始对象对应的map则创建并于整个数据结构进行关联
if (!depMap) {
depMap = new Map();
bucket.set(target, depMap);
}
// 取出对象key对应的副作用函数的集合
let deps = depMap.get(key);
if (!deps) {
deps = new Set();
depMap.set(key, deps);
}
deps.add(activeFn);
}
// 在set中触发依赖
function trigger(target, key) {
const depMap = bucket.get(target);
if (!depMap) return;
const effects = depMap.get(key);
if (!effects) return;
effects.forEach((fn) => {
fn();
});
}
const newObj = new Proxy(obj, {
get(target, key) {
track(target, key);
return target[key];
},
// 在set操作中,赋值,然后调用effect函数
set(target, key, value) {
target[key] = value;
trigger(target, key);
return true;
},
});
function effect0() {
console.log("effect0", "----", newObj.text);
}
effect(effect0);
newObj.text = "hello Vue3";