Dep、Watcher、Observer主要解决两个问题
- 如何实现依赖的收集?
- 依赖发生变化时如何通知页面更新?
三者之间的关系:
data中的每一个属性对应一个dep,dep中的subs保存了依赖该属性的watcher,每一个watcher有一个update方法,该方法会在依赖的属性的值发生变化的时候调用。
一个对象或数组对应一个__ob__属性即observer实例。由observer(data)开始,在observer中执行new Observer(value)操作,new Observer中遍历value对象,为每一个值defineReactive,在defineReactive中默认会对观测的data对象进行深度观测,即会递归观测属性值,如果属性值时对象或数组的话。此时的watcher只有一个,即为渲染watcher,所有的dep都会添加到该watcher的deps中。
综上,三者之间的关系为一个渲染watcher对应一个组件,该watcher中的deps保存了data中所有属性值的dep,wacther中保存deps的目的是为了依赖取消时移除dep实例中subs记录的watcher,每个dep实例有一个subs属性,该属性用于保存依赖产生dep实例的data属性的watcher,dep由observer产生,一个对象或数组对应一个observer实例,每一个属性对应一个dep。
随便写点代码来帮助理解:
/** data */
const data = {
name: 'nick',
age: 25,
job: 'Front-end Developer'
};
/** utils */
function defineReactive(obj, key, val) {
const dep = new Dep();
Object.defineProperty(obj, key, {
enumerable: false,
configurable: true,
get: function reactiveGetter() {
console.log(`${key}使用到了,需要收集起来`);
if (Dep.target) {
dep.depend();
}
return val;
},
set: function reactiveSetter(newVal) {
if (newVal === val) {
return;
}
console.log(`${key}被设置了,需要通知依赖它的观察者更新哦`);
val = newVal;
dep.notify();
}
})
}
function def(obj, key, val, enumerable) {
Object.defineProperty(obj, key, {
value: val,
enumerable: !!enumerable,
configurable: true,
writable: true
})
}
function isObject(value) {
return Object.prototype.toString.call(value) === '[object Object]';
}
function createElement(tag) {
return document.createElement(tag);
}
/** observer */
class Observer{
constructor(value) {
def(value, '__ob__', this);
this.walk(value);
}
walk(obj) {
const keys = Object.keys(obj);
let l = keys.length, i = 0;
for (; i < l; i++) {
defineReactive(obj, keys[i], obj[keys[i]])
}
}
}
function observer(obj) {
if (!isObject(obj)) {
return;
}
let ob = obj.__ob__;
if (ob) {
console.log('对象已经被观测过了');
return ob;
}
ob = new Observer(obj);
return ob;
}
/** watcher */
class Watcher {
constructor(fn) {
this.deps = [];
this.getter = fn;
this.get();
}
get() {
pushStack(this);
this.getter(data);
popStack();
}
addDep(dep) {
this.deps.push(dep);
dep.addSub(this);
}
update() {
this.getter(data);
}
}
/** dep */
class Dep {
static target;
constructor() {
this.subs = [];
}
addSub(sub) {
this.subs.push(sub);
}
notify() {
for (let i = 0; i < this.subs.length; i++) {
this.subs[i].update();
}
}
depend() {
if (Dep.target) {
Dep.target.addDep(this);
}
}
}
Dep.target = null;
const targetStack = [];
function pushStack(target) {
targetStack.push(target);
Dep.target = target;
}
function popStack() {
targetStack.pop();
Dep.target = targetStack[targetStack.length - 1];
}
/** render */
observer(data);
const watcher = new Watcher(_render);
console.log(watcher);
function _render(data) {
const _c = createElement;
const p1 = _c('p');
p1.textContent = data.name;
const p2 = _c('p');
p2.textContent = String(data.age);
const div = _c('div');
div.setAttribute('id', 'app');
div.appendChild(p1);
div.appendChild(p2);
const oldDiv = document.getElementById('app');
console.log(oldDiv);
if (oldDiv) {
document.body.replaceChild(div, oldDiv);
}
else {
document.body.appendChild(div);
}
}