深入解析vue.js響應式原理與實現
阿新 • • 發佈:2018-11-28
vue.js響應式原理解析與實現。angularjs是通過髒檢查來實現資料監測以及頁面更新渲染。之後,再接觸了vue.js,當時也一度很好奇vue.js是如何監測資料更新並且重新渲染頁面。vue.js響應式原理解析與實現
Object.defineProperty
es5新增了Object.defineProperty這個api,它可以允許我們為物件的屬性來設定getter和setter,從而我們可以劫持使用者對物件屬性的取值和賦值。比如以下程式碼:
const obj = { }; let val = 'cjg'; Object.defineProperty(obj, 'name', { get() { console.log('劫持了你的取值操作啦'); return val; }, set(newVal) { console.log('劫持了你的賦值操作啦'); val = newVal; } }); console.log(obj.name); obj.name = 'cwc'; console.log(obj.name); //歡迎加入全棧開發交流圈一起學習交流:864305860
我們通過Object.defineProperty劫持了obj[name]的取值和賦值操作,我們可以在obj[name]被賦值的時候觸發更新頁面操作。
釋出訂閱模式
當事件發生的時候,釋出者通知所有訂閱該事件的訂閱者。我們來看一個例子瞭解下。
class Dep { constructor() { this.subs = []; } // 增加訂閱者 addSub(sub) { if (this.subs.indexOf(sub) < 0) { this.subs.push(sub); } } // 通知訂閱者 notify() { this.subs.forEach((sub) => { sub.update(); }) } } const dep = new Dep(); const sub = { update() { console.log('sub1 update') } } const sub1 = { update() { console.log('sub2 update'); } } dep.addSub(sub); dep.addSub(sub1); dep.notify(); // 通知訂閱者事件發生,觸發他們的更新函式
vue.js首先通過Object.defineProperty來對要監聽的資料進行getter和setter劫持,當資料的屬性被賦值/取值的時候,vue.js就可以察覺到並做相應的處理。
class Observer { constructor(data) { // 如果不是物件,則返回 if (!data || typeof data !== 'object') { return; } this.data = data; this.walk(); } // 對傳入的資料進行資料劫持 walk() { for (let key in this.data) { this.defineReactive(this.data, key, this.data[key]); } } // 建立當前屬性的一個釋出例項,使用Object.defineProperty來對當前屬性進行資料劫持。 defineReactive(obj, key, val) { // 建立當前屬性的釋出者 const dep = new Dep(); /* * 遞迴對子屬性的值進行資料劫持,比如說對以下資料 * let data = { * name: 'cjg', * obj: { * name: 'zht', * age: 22, * obj: { * name: 'cjg', * age: 22, * } * }, * }; * 我們先對data最外層的name和obj進行資料劫持,之後再對obj物件的子屬性obj.name,obj.age, obj.obj進行資料劫持,層層遞迴下去,直到所有的資料都完成了資料劫持工作。 */ new Observer(val); Object.defineProperty(obj, key, { get() { // 若當前有對該屬性的依賴項,則將其加入到釋出者的訂閱者佇列裡 if (Dep.target) { dep.addSub(Dep.target); } return val; }, set(newVal) { if (val === newVal) { return; } val = newVal; new Observer(newVal); dep.notify(); } }) } } // 釋出者,將依賴該屬性的watcher都加入subs陣列,當該屬性改變的時候,則呼叫所有依賴該屬性的watcher的更新函式,觸發更新。 class Dep { constructor() { this.subs = []; } addSub(sub) { if (this.subs.indexOf(sub) < 0) { this.subs.push(sub); } } notify() { this.subs.forEach((sub) => { sub.update(); }) } } Dep.target = null; // 觀察者 class Watcher { /** *Creates an instance of Watcher. * @param {*} vm * @param {*} keys * @param {*} updateCb * @memberof Watcher */ constructor(vm, keys, updateCb) { this.vm = vm; this.keys = keys; this.updateCb = updateCb; this.value = null; this.get(); } // 根據vm和keys獲取到最新的觀察值 get() { Dep.target = this; const keys = this.keys.split('.'); let value = this.vm; keys.forEach(_key => { value = value[_key]; }); this.value = value; Dep.target = null; return this.value; }//歡迎加入全棧開發交流圈一起學習交流:864305860 update() { const oldValue = this.value; const newValue = this.get(); if (oldValue !== newValue) { this.updateCb(oldValue, newValue); } } } let data = { name: 'cjg', obj: { name: 'zht', }, }; new Observer(data); // 監聽data物件的name屬性,當data.name發現變化的時候,觸發cb函式 new Watcher(data, 'name', (oldValue, newValue) => { console.log(oldValue, newValue); }) data.name = 'zht'; // 監聽data物件的obj.name屬性,當data.obj.name發現變化的時候,觸發cb函式 new Watcher(data, 'obj.name', (oldValue, newValue) => { console.log(oldValue, newValue); }) data.obj.name = 'cwc'; data.obj.name = 'dmh';
這樣,一個簡單的響應式資料監聽就完成了。當然,這個也只是一個簡單的demo,來說明vue.js響應式的原理,真實的vue.js原始碼會更加複雜,因為加了很多其他邏輯。