Vue.js 原理
vue原理
必看:https://segmentfault.com/a/1190000006599500#articleHeader3
vue採用資料劫持並結合釋出者-訂閱者模式的方式實現雙向資料繫結的。
資料劫持
通過Object.defineProperty()
來劫持各個屬性的setter
,getter
Object.defineProperty
方法會=能夠在一個物件上直接定義一個新屬性,或者修改一個物件的現有屬性,並返回這個物件。
Object.defineProperty
語法
Object.defineProperty(obj, prop, descriptor)// obj 要在其上定義屬性的物件 // prop 要定義或修改的屬性名稱 // descriptor 將被定義或者修改的屬性描述 // 返回值為被傳遞給函式的物件
訪問器屬性
訪問器屬性是物件中一種特殊屬性,它不能直接在物件中設定,而且必須通過Ojbect.defineProperty()
var obj = {} Object.defineProperty(obj,'red',{ get: function () { console.log('get方法被呼叫了'); }, set: function(value) { console.log('set方法被呼叫,引數為' + value); } }); console.log(obj); // {} obj.red; // get方法被呼叫了 obj.red = 'green'; // set方法被呼叫,引數為green
get 和 set 方法內部的this都是指向obj的,這就意味著get和set函式可操作物件內部的值。另外,訪問器屬性的值會“覆蓋”同名的普通屬性,這是因為訪問器屬性會被優先訪問,語氣同名的普通屬性則會被忽略。
<input type="text" id="inp"> <p id="par"></p> <script> varobj = {} Object.defineProperty(obj, 'red', { set: function (v) { document.getElementById('inp').value = v document.getElementById('par').innerHTML = v } }); document.addEventListener('keyup',function(ev){ obj.red = ev.target.value // 獲取事件目標裡最先觸發的元素 }) console.log(obj); // {} </script>
簡單模擬vue雙向資料繫結
思路分析:
-
實現一個數據監聽器Observer,能夠對資料物件的所有屬性進行監聽,如果變動,可以拿到最新值並通知訂閱者。
-
實現一個指令解析器Compile,對每個元素節點的志林進行掃描和解析,根據志林模板替代資料,以及繫結的相應的更新函式。
-
實現一個Watcher,作為連線Observer和Compile的橋樑,能夠訂閱並受到每個屬性變動的通知,執行志林繫結的相應回撥函式,從而更新檢視。
-
mvvm入口函式,整合以上三者。
1、實現Observer
認識Object.keys()
獲取物件的鍵名,返回陣列
var data = { name : 'king', age: 20, gender: 'boy' } console.log(Object.keys(data)) // ["name", "age", "gender"]
實現監聽者Observer
我們可以利用Object.defineProperty()
來監聽屬性變化,那麼將需要observe的資料進行遞迴遍歷,包括他子屬性物件的屬性,都加上setter
和getter
那麼,如果給物件的某個屬性賦值時,就會觸發setter
var obj = {name: 'houfee'} // 監聽器監聽資料變化 function observe(data) { // 判斷是否為物件 if (!data || typeof data !== 'object') { return; } // 遍歷物件的鍵名,每次遍歷鍵名呼叫dafineReactive方法 Object.keys(data).forEach(function (key) { dafineReactive(data, key, data[key]); }) } // defineProperty() 監聽資料變化 function dafineReactive(data, key, val) { observe(val); // 監聽子屬性 Object.defineProperty(data, key, { enumerable: true, configurable: false, get: function () { return val; }, set: function (newVal) { console.log('監聽到屬性變化了', val, '---->', newVal); val = newVal; } }); } console.log(obj); // {name: "houfee"} // 呼叫Observer監聽方法 observe(obj) // 使用定時器驗證監聽 setTimeout(function () { console.log('定時器監聽變化'); obj.name = '一直走' console.log(obj); }, 2000)
// defineProperty() 監聽資料變化 function dafineReactive(data, key, val) { var dep = new Dep(); // 訂閱者 observe(val); // 監聽子屬性 Object.defineProperty(data, key, { enumerable: true, configurable: false, get: function () { return val; }, set: function (newVal) { if (val === newVal) return; console.log('監聽到屬性變化了', val, '---->', newVal); val = newVal; dep.notify(); // 每次資料變動通知訂閱者 } }); } function Dep() { // 維護一個數組,收集訂閱者 this.subs = []; } Dep.prototype = { addSub: function (sub) { this.subs.push(sub); }, // 資料變動觸發notify,呼叫訂閱者的update方法 notify: function () { this.subs.forEach(function (sub) { sub.update(); }) } }
Object.defineProperty(data, key, { enumerable: true, configurable: false, get: function () { // 由於需要在閉包內新增watcher,所以 //通過Dep定義一個全域性target屬性,暫存watcher, 新增完移除 Dep.target && dep.addDep(Dep.target) return val; }, set: function (newVal) { if (val === newVal) return; console.log('監聽到屬性變化了', val, '---->', newVal); val = newVal; dep.notify(); // 每次資料變動通知訂閱者 } }); // Watcher.js watcher.prototype = { get: function (key) { Dep.target = this; // 這裡會觸發屬性的getter,從而新增訂閱者 this.value = data[key]; Dep.target = null; } }
這裡已經實現了一個Observer了,已經具備了監聽資料和資料變化通知訂閱者的功能。
2、實現Compile
Compile主要實現解析模板指令,將模板中的變數替換成資料,然後初始化渲染頁面,並肩每個指令對飲的節點繫結更新函式,新增監聽資料的訂閱者,一旦資料有變動,收到通知,更新檢視: