响应式是vue的一个重要组成部分,它实现了数据与视图的绑定。接下我们就从相应数据跟副作用函数开始一步步了解vue的响应系统的设计与实现
副作用函数
副作用函数是指会产生副作用的函数
function effect(){
document.body.innerText = "hellow";
}
当effect执行时,会改变body的内容,但除了effect,其他函数也可以访问到body的文本内容,所以effect函数会影响到其他函数读取body的内容,即说effect是一个副作用函数。
let a = 1
function effect(){
var a = 2
}
副作用函数非常常见,像这种effect改变了其他函数也可以访问到的全局变量,影响了其他函数对它的读取,这也是一种副作用函数。可以理解为某个函数改变了其他函数对某个内容的访问结果,即这个函数是副作用函数
响应式数据
const obj = {text:'hellow'}
function effect (){
document.body.innerText = obj.text;
}
当effect函数执行时,会改变body的文本内容。如果obj的值变化的时候都能触发effect,从而改变body的文本内容达到改变视图的效果,那我们则称obj是一个响应数据。
改变obj的时候,我们会触发到它的set操作,而读取的时候会触发到get操作。
如果我们可以拦截这两步操作,在读取obj.text的时候把副作用函数effect存储到一个桶里,在修改obj.text的时候再把这个副作用函数拿出来执行,便可以达到每当修改obj.text的时候都能改变视图的效果了。
在vue2中,vue采用了Object.defineProperty实现了响应系统,但Object.defineProperty只能监听指定key的value的变化,所以需要遍历整个对象从而监听所有key。vue3使用了Proxy代替了defineProperty解决了这一痛点。
这里我们用proxy来实现
//用于存储副作用函数的桶
const bucket = new Set();
//原始数据
const data = { text: "hellow" };
//对原始数据的代理
const obj = new Proxy(data, {
get(target, key) {
//将副作用函数丢进bucket
if (effect) {
bucket.add(effect);
}
//返回读取的值
return target[key];
},
set(target, key, newVal) {
//设置新属性值
target[key] = newVal;
//把副作用函数从桶取出并执行
bucket.forEach((fn) => fn());
//设置成功
return true;
},
});
这时候执行effect之后,再改变obj.text的值的时候,会发数据响应视图,视图改变了。成功实现了一个响应数据。
注册副作用函数
在上面代码中,我们使用effect作为副作用函数,但实际中副作用函数不一定这么命名,甚至可能是一个匿名函数,所以我们需要一个注册副作用函数的流程
//用一个全局变量存储被注册的副作用函数
let activeEffect;
//用于注册副作用函数
function effect(fn) {
activeEffect = fn;
fn();
}
const obj = new Proxy