Appearance
六、依赖收集
- 每个属性都要有一个dep,用来收集watcher
- 每个dep中存放着多个watcher
- 同一个watcher会被多个dep所记录
- dep与watcher是多对多的关系
1、定义Dep类
js
// src\observer\dep.js
// 唯一标识
let id = 0;
class Dep {
constructor() {
this.subs = []; // 用于存储watcher
this.id = id++; // 标识dep的唯一性,用于防止重复取值添加dep
}
depend() {
// watcher也可以存放dep,实现双向记忆,让watcher记住dep的同时,让dep也记住watcher
Dep.target.addDep(this);
}
addSub(watcher) {
this.subs.push(watcher);
}
notify() {
this.subs.forEach(watcher => watcher.update());
}
}
// 静态属性
Dep.target = null;
export function pushTarget(watcher) {
// 保留watcher
Dep.target = watcher;
}
export function popTarget() {
// 将变量删除掉
Dep.target = null;
}
export default Dep;2、定义Watcher类
js
// src\observer\watcher.js
import { pushTarget, popTarget } from "./dep";
let id = 0;
class Watcher {
constructor(vm, exprOrFn, cb, options) {
this.vm = vm;
this.exprOrFn = exprOrFn; // 渲染函数
this.cb = cb; // 回调函数
this.options = options;
this.id = id++; //watcher的唯一标识
this.deps = []; // watcher记录有多少dep依赖
this.depsId = new Set(); // 用于去重dep
if (typeof exprOrFn == 'function') {
this.getter = exprOrFn;
}
// 默认调用get方法
this.get();
}
addDep(dep) {
let id = dep.id;
// 去重
if (!this.depsId.has(id)) {
this.deps.push(dep);
this.depsId.add(id);
dep.addSub(this);
}
}
// 利用JS的单线程机制,先存储watcher,然后更新,最后删除watcher
get() {
// 存储当前watcher实例
pushTarget(this);
// 调用exprOrFn,渲染页面 > 取值(执行了get方法)
this.getter();
// 渲染完成后,将watcher删掉
popTarget();
}
run() {
// 渲染逻辑
this.get();
}
update() {
// 重新渲染
this.get();
}
}
export default Watcher;3、对象依赖收集
js
// src\observer\index.js
// 每个属性都有一个dep
let dep = new Dep();
// 当页面取值时,说明这个值用来渲染了,将这个watcher和这个属性对应起来
Object.defineProperty(data, key, {
get() {
// 如果取值时有watcher
if (Dep.target) {
// 让watcher保存dep,并且让dep 保存watcher
dep.depend();
}
return value
},
set(newValue) {
if (newValue == value) return;
observe(newValue);
value = newValue;
// 通知渲染watcher去依赖更新
dep.notify();
}
})4、数组依赖收集
- 获取arr的值,会调用get方法,就让当前数组记住渲染watcher
- 给所有的对象类型都增加一个dep属性
- 当页面对arr取值时,让这个数组的dep记住这个watcher
- 当操作push, shift等更新数组的方法时,就找到数组对应的watcher来更新
js
// src\observer\index.js
// 1、构造函数中添加一个dep
class Observer {
constructor(value) {
this.dep = new Dep(); // value = {} / []
......
}
}
// 2、定义响应式中进行添加依赖
function defineReactive(data, key, value) {
// 获取到数组对应的dep
let childDep = observe(value);
// 每个属性都有一个dep
let dep = new Dep();
// 当页面取值时,说明这个值用来渲染了,将这个watcher和这个属性对应起来
Object.defineProperty(data, key, {
get() {
// 如果取值时有watcher
if (Dep.target) {
// 让watcher保存dep,并且让dep 保存watcher
dep.depend();
// 可能是数组,可能是对象
if (childDep) {
// 默认给数组增加了一个dep属性,当对数组这个对象取值的时候
childDep.dep.depend(); // 数组存起来了渲染watcher
}
}
return value
},
set(newValue) {
if (newValue == value) return;
observe(newValue);
value = newValue;
// 通知渲染watcher去依赖更新
dep.notify();
}
})
}
// src\observer\array.js
// 3、重写方法中进行调用更新
arrayMethods[method] = function (...args) {
// ...
ob.dep.notify()
return result;
}5、调用渲染函数进行依赖更新
渲染流程
- 先把这个渲染watcher放到Dep.target属性上
- 开始渲染,取值会调用get方法,需要让这个属性的dep 存储当前的watcher
- 页面上所属需要的属性都会将这个watcher存在自己的dep中
- 等会属性更新了,就重新调用渲染逻辑,通知自己存储的watcher来更新
js
// src\lifecycle.js
export function mountComponent(vm, el) {
vm.$el = el;
callHook(vm, 'beforeMount');
// 定义更新函数
let updateComponent = () => {
vm._update(vm._render());
};
// 初始化就会创建watcher
new Watcher(vm, updateComponent, () => {
callHook(vm, 'updated');
}, true);
callHook(vm, 'mounted');
}