vue2 主要是基于 Object.defineProperty 做的数据双向绑定,这里从 observer、watcher、dep 出发做一个简单的数据绑定实例
主要步骤
- observer 递归监听 data 对象的所有属性
- watcher 订阅器,视图、指令等需要绑定某个属性时会新建一个订阅器,并加入 Dep
- Dep 存放绑定某个属性的所有订阅器,可以添加订阅器和通知变化
- 数据更改时,通知 Dep 去执行相应的更新函数
Dep
订阅器集合,也就是绑定了某个属性的视图或其他对象的集合。
class Dep {
constructor() {
this.deps = [];
}
// 添加订阅器
addDep(dep) {
this.deps.push(dep);
}
// 更新所有订阅器
notify(newData) {
for (let dep of this.deps) {
dep.update(newData);
}
}
}
Observer
递归监听 data 对象的属性
// defineReactive 监听对象属性
function defineReactive(obj, key, val) {
// deps 订阅器集合,作为闭包,方便处理订阅器的添加和更新操作
let deps = new Dep();
Object.defineProperty(obj, key, {
get() {
// 判断是否需要添加订阅器
// Dep.target 用于暂存订阅器
Dep.target && deps.addDep(Dep.target);
},
set(newData) {
// 对比数据,如果变化了就通知订阅器
if (newData !== val) {
deps.notify(newData);
}
},
});
}
function observer(data) {
if (!data || typeof data !== "object") {
return null;
}
for (key in data) {
if (data.hasOwnProperty(key)) {
if (typeof data[key] === "object") {
// 递归监听
observer(data[key]);
}
defineReactive(data, key, data[key]);
}
}
}
Watcher
- 绑定数据时,需要新建一个 watcher 实例,比如视图绑定、computed、watch
- 订阅器初始化时,会自动将自己添加进 deps 里
// Watcher 订阅器
class Watcher {
constructor(id, vm, key, cb) {
console.log(`订阅器${id}`);
this.id = id; // 订阅器标识
this.vm = vm; // 当前环境
this.cb = cb; // 更新回调
this.key = key; // 当前监听属性
// 触发getter,将自身添加到属性的订阅器数组deps中
Dep.target = this;
let val = vm.data[key]; // 强制执行 get,加入到订阅器集合
Dep.target = null;
}
update(newData) {
console.log(`订阅器${this.id}数据更新了,newData: ${newData}`);
// this.cb()
}
}
触发更新
// 新增订阅器
this.data = {
name: "jack",
action: "eating",
};
observer(this.data);
new Watcher(1, this, "name");
new Watcher(2, this, "action");
this.data.name = "Tom";