响应式理解初步
什么是响应式?先来看一段代码:
let a = 10;
let b = a + 10;
console.log(b); // 20
a = 20;
console.log(b); // 20
这段代码中,我们声明了两个number类型变量,b这个变量依赖于a。
现在我们的需求是:当 a 发生改变, b 自动也相应发生改变,这就是响应式数据。
但是在上面这个代码中,我们发现 b 并没有随之改变。因此也就没有实现响应式。
其实,最简单的办法就是每当 a 发生改变, 我们人为地调用 b = a + 10,但作为精明的程序员,这显得也太愚蠢了。
响应式原理探究
这里我们先探究基本数据类型来实现响应式。
之前我们提到过,在上面的代码中,b 是依赖于 a 的, 而这个依赖关系是 b = a + 10; 那么可以想到的思路是:当我们重新设置 a 的值的时候,我们重新调用一遍这个依赖关系。
但是我们可以预想到的是:在复杂庞大的项目中,依赖关系可不止这么简简单单。
因此这里提出一个概念叫依赖收集:将和基变量有联系的依赖关系存储到一个集合中;当我们重新给基变量重新赋值的时候,我们依次调用所有的依赖关系,这叫做依赖触发。
最简单的响应式原理模型(基本数据类型)
基于上面的原理探究,我们其实可以设想到的是:将一个基本数据类型包装成一个类,通过这个类中的set和get方法来对这个变量的设置进行拦截!而在这个拦截过程中,我们就可以触发这些依赖。
代码:
// 这个就是对基本数据类型来转换成响应式对象的一个包装类
class Dep {
// 构造器函数接受一个initial value,作为一个初始值
constructor(initVal) {
this.effects = new Set(); // 这个set集合作为依赖的容器
this._val = initVal; // 给this._val赋值,_val意味“私有”变量
}
get value() { // 获取dep.value
return this._val;
}
set value(newV) { // 当对dep.value赋值的时候会调用这里的函数
this._val = newV; // 重新赋值
this.notify(); // 调用notify
}
depend(effect) { // 收集依赖
this.effects.add(effect);
}
notify() { // 触发已经收集的依赖
this.effects.forEach((effect) => {
effect(this);
});
}
}
function effectWatch(effect, dep) { // 定义依赖的方法
effect(dep); // 初次调用依赖关系
dep.depend(effect); // 添加依赖
}
// 响应式例子:
const a = new Dep(10);
let b; // 声明一个变量,依赖于a
effectWatch((a) => {
b = a.value + 20; // 依赖关系
console.log(b); // 打印b
}, a);
a.value = 25;
a.value = 40
// 30 调用effectWatch时打印
// 45 将a.value设置成25时打印
// 60 将a.value设置成40时打印
调用逻辑:
初始化a为一个Dep(响应式)对象 -----> 定义依赖变量b -----> 调用effectWatch来定义依赖关系和初始化b -----> a.value 重新赋值 -----> 调用 set value 方法 -----> 重新赋值 -----> 调用notify,触发已经收集的依赖。