Dep、Watcher、Observer主要解決兩個問題javascript
- 如何實現依賴的收集?
- 依賴發生變化時如何通知頁面更新?
三者之間的關係:java
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中。react
綜上,三者之間的關係爲一個渲染watcher對應一個組件,該watcher中的deps保存了data中全部屬性值的dep,wacther中保存deps的目的是爲了依賴取消時移除dep實例中subs記錄的watcher,每一個dep實例有一個subs屬性,該屬性用於保存依賴產生dep實例的data屬性的watcher,dep由observer產生,一個對象或數組對應一個observer實例,每個屬性對應一個dep。數組
隨便寫點代碼來幫助理解:app
/** 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);
}
}
複製代碼