寫在前面:本文爲我的在平常工做和學習中的一些總結,便於後來查漏補缺,非權威性資料,請帶着本身的思考^-^。
前文連接:瞭解一下Vue - [Vue是怎麼實現響應式的(一)]
瞭解一下Vue - [Vue是怎麼實現響應式的(二)]
前面對於響應式有了一些些瞭解,這裏嘗試本身寫一遍(抄一遍)
設對象data爲要被定義的響應式對象,key爲data中的屬性html
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(); };
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(); } } }
用來爲data[key]定義getter/settersegmentfault
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(); }, }); };
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屬性,頁面成功進行了更新。。。app
初始化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(通知)它,執行相應的方法;ui
原理大抵如此,細節還有不少this
THE ENDlua