寫在前面:本文爲個人在日常工作和學習中的一些總結,便於後來查漏補缺,非權威性資料,請帶着自己的思考^-^。
前文鏈接:瞭解一下Vue - [Vue是怎麼實現響應式的(一)]
瞭解一下Vue - [Vue是怎麼實現響應式的(二)]
前面對於響應式有了一些些瞭解,這裏嘗試自己寫一遍(抄一遍)
設對象data爲要被定義的響應式對象,key爲data中的屬性
- 通過defineProperty爲data中的每一個屬性key定義getter/setter,這樣在data[key]被引用和被賦值時將觸發相應的操作,這個是響應式實現的基礎;
- dep對象,作爲依賴實體,其包含依賴添加、依賴者(watcher)添加、變更通知(notify)等方法,用於依賴收集、變更分發,本身包含觀察者列表;
- watcher對象,觀察者對象,包含依賴收集、更新方法;用於依賴收集、更新,包含依賴列表;
Dep構造函數
let uid = 0;
class Dep {
constructor() {
this.id = ++uid;
this.subs = [];
}
addSub(target) { // 將target添加進觀察者列表
this.subs.push(target);
}
depend() { //
if (Dep.target) {
Dep.target.addDep(this);
}
}
removeSub(target) {
this.subs.splice(this.subs.findIndex(_ => _.id == target.id), 1);
}
notify() {
const subs = this.subs.slice();
for (let i = 0; i < subs.length; i++) {
subs[i].update();
}
}
}
Dep.target = null; // 當前處於激活狀態的watcher
const targetStack = []; // 存放watcher的棧
const pushTarget = target => { // 更新當前激活的Dep.target
if (Dep.target) {
targetStack.push(target); // 將target(watcher)壓入棧中
}
Dep.target = target;
};
const popTarget = () => { // 從watcher棧中彈出頂部watcher
Dep.target = targetStack.pop();
};
watcher構造函數
class Watcher {
constructor(getter, options = {}) {
this.deps = []; // 依賴列表
this.newDeps = []; // 最近一次添加的依賴列表
this.depIds = new Set(); // 依賴ids列表
this.newDepIds = new Set(); // 最近一次添加的依賴ids列表
this.getter = getter; //
this.lazy = !!options.lazy; // 懶依賴,在首次實例化的時候不執行getter
this.dirty = this.lazy; // 髒值標識,主要用在computed計算時;
this.lazy ? undefined : this.get();
}
get() {
pushTarget(this); // 將當前watcher作爲激活的watcher對象,並推入targetStack棧中
const value = this.getter();
popTarget(); // 將當前watcher置爲棧中上一個watcher
this.cleanupDeps(); // 依賴整理,主要用來整理this.deps、this.depIds
return value;
}
addDep(dep) {
if (!this.newDepIds.has(dep.id)) {
this.newDepIds.add(dep.id);
this.newDeps.push(dep);
if (!this.depIds.has(dep.id)) {
dep.addSub(this);
}
}
}
cleanupDeps() {
let i = this.deps.length;
while (i--) {
const dep = this.deps[i];
if (!this.newDepIds.has(dep.id)) { // 如果新的依賴列表中不再包含之前的依賴項,則調用dep.removeSub方法,將當前watcher從dep.subs列表中移除
dep.removeSub(this);
}
}
[this.deps, this.newDeps] = [this.newDeps, this.deps];
this.newDeps.length = 0;
[this.depIds, this.newDepIds] = [this.newDepIds, this.depIds];
this.newDepIds.clear();
}
evaluate() {
this.value = this.get();
this.dirty = false;
}
update() {
if (this.lazy) {
this.dirty = true;
} else {
new Promise((resolve) => {
resolve();
}).then(() => {
this.get();
});
}
}
depend() { // 將當前watcher的依賴添加到當前Dep.target的依賴列表中
const deps = this.deps;
for (let i = 0; i < deps.length; i++) {
deps[i].depend();
}
}
}
defineReactive 方法
用來爲data[key]定義getter/setter
const defineReactive = (target, key, val) => {
const dep = new Dep(); // 這裏實例化dep對象,用於在getter/setter觸發的時候訪問該對象進行依賴收集等操作,本質上來說當前實例化的dep和當前的data[key]一一對應了
Object.defineProperty(target, key, {
enumerable: true,
configurable: true,
get() {
if (Dep.target) {
dep.depend(); // 依賴添加
}
return val;
},
set(newVal) {
if (val === newVal) return;
val = newVal;
dep.notify();
},
});
};
data、computed初始化
const initData = data => {
const keys = Object.keys(data);
for (let i = 0; i < keys.length; i++) {
defineReactive(vm, keys[i], data[keys[i]]);
}
};
const initComputed = computed => {
const keys = Object.keys(computed);
for (let i = 0; i < keys.length; i++) {
const userDef = computed[keys[i]].bind(vm);
const watcher = new Watcher(userDef, { lazy: true });
Object.defineProperty(vm, keys[i], {
enumerable: true,
configurable: true,
get() {
if (watcher) {
if (watcher.dirty) {
watcher.evaluate();
}
if (Dep.target) {
watcher.depend();
}
return watcher.value;
}
},
});
}
};
模擬頁面渲染:
<!-- html -->
<body>
<div id="app"></div>
<button id="btn">更新</button>
</body>
const data = initData(vm.data);
const computed = initComputed(vm.computed);
const updateComponent = () => {
const app = document.querySelector('#app');
var a = vm.current; // 引用vm.current
var b = vm.computedCurrent; // 應用computed
app.innerHTML = `data: <i>${a}</i> computed: <strong>${b}</strong>`;
};
defineReactive(vm, 'current', vm.current); // 爲vm.current定義getter/setter
const watcher = new Watcher(updateComponent);
可以看到點擊按鈕,更新vm中的current屬性,頁面成功進行了更新。。。
流程分析
初始化data屬性getter/setter --> 實例化watcher,帶有update方法、addDep方法 --> watcher.get方法執行進行依賴收集(該方法中被引用到的data屬性視爲當前watcher的依賴) --> watcher依賴收集的過程中被依賴的data屬性也會進行觀察者收集 --> data屬性更新 --> 通知watcher,update方法調用 --> 新一輪的依賴收集,新舊依賴比較,新依賴相對舊依賴缺失的從依賴列表中刪除,新增的加入依賴列表同時將觀察者watcher添加進該依賴的subs觀察者列表中 --> 執行業務代碼(視圖更新);
對於computed來說,它既是觀察者,也是依賴;視圖更新watcher依賴computed,computed依賴data;
對於watch來說,它是觀察者,依賴要watch的data或者computed,依賴更新時會notify(通知)它,執行相應的方法;
原理大抵如此,細節還有很多
THE END