MVC,MVP,MVVM三種軟件架構設計模式的對比:
MVC:
- M(model):數據模型,渲染模板
- V(view):視圖模板(用戶界面UI)
- C(controller):控制器,修改數據
MVP:
- M(model):數據模型
- V(view): 視圖模板
- P(presenter):控制展示器,修改數據,渲染模板
在MVP中View並不直接使用Model,它們之間的通信是通過Presenter (MVC中的Controller)來進行的。
MVVM:
- M(model):數據模型
- V(view):視圖模板
- VM(viewModel):視圖模型,修改數據,自動渲染模板
MVVM模式和MVP一致,MVVM可以實現數據雙向綁定,viewModel就是view和model的binder連接器,通過它來實現雙向綁定。
數據綁定原理:
實現方法有這幾種:發佈者-訂閱者模式(backbone.js)髒值檢查(angular.js) 數據劫持(vue.js)
這裏簡析一下vue.js的數據綁定原理,
vue.js採用數據劫持結合發佈者-訂閱者的方式,通過Object.defineProperty()來劫持各個屬性的setter,getter,在數據變動時,發佈消息給訂閱者,觸發相應的監聽回調。通過Directives指令去對DOM做封裝,當數據發生變化,會通知指令去修改對應的DOM,數據驅動DOM的變化。vue.js還會對操作做一些監聽(DOM Listener),當我們修改視圖的時候,vue.js監聽到這些變化,從而改變數據。這樣就形成了數據的雙向綁定。
存取描述符均具有以下可選鍵值:
configurable:爲true時屬性可配置
enumerable:爲true時屬性可枚舉
get:給屬性提供getter方法,取得數據時回調的函數,調用時不傳參,傳this對象
set:給屬性提供setter方法,設置數據值時回調的函數,調用時傳入一個參數,即屬性新的值
關於Object.defineProperty:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
proxy代理器:ES6新方法,在目標對象之前架設一層“攔截”,外界對該對象的訪問,都必須先通過這層攔截,因此提供了一種機制,可以對外界的訪問進行過濾和改寫。
觀察者模式和發佈/訂閱者模式的差異:
- Observer模式要求觀察者必須訂閱內容改變的事件,定義了一個一對多的依賴關係;
- Publish/Subscribe模式使用了一個主題/事件通道,這個通道介於訂閱着與發佈者之間;
- 觀察者模式裏面觀察者「被迫」執行內容改變事件(subject內容事件);發佈/訂閱模式中,訂閱着可以自定義事件處理程序;
- 觀察者模式兩個對象之間有很強的依賴關係;發佈/訂閱模式兩個對象之間的耦合度底。
具體實現步驟:
●Observer 對observe的數據對象進行遞歸遍歷,包括子屬性對象的屬性,都加上setter getter,監聽數據變化
● compile解析模板指令,將模板中的變量替換成數據,接着初始化渲染頁面視圖,並將每個指令對應的節點綁定更新函數,添加監聽數據的訂閱者。一旦數據有變動,訂閱者收到通知,就會更新視圖。
● Watcher訂閱者是Observer和Compile之間通信的橋樑,主要負責:
1)在自身實例化時,往屬性訂閱器(Dep)裏面添加自己
2)自身必須有一個update()方法
3)待屬性變動,dep.notice()通知時,就調用自身的update()方法,並觸發Compile中綁定的回調
● viewModel作爲數據綁定的入口,整合Observer、Compile、Watcher三者,通過Observer來監聽自己的model數據變化,通過Compile來解析編譯模板指令,最終利用Watcher搭起Observer和Compile之間的通信橋樑,達到數據變化的雙向綁定效果。
使用Object.defineProperty()來實現簡單的雙向綁定小例子。
<div id="app">
<input type="text" id="input">
<span id="text"></span>
</div>
var obj = {};
Object.defineProperty(obj, 'prop', {
get: function() {
return val;
},
set: function(newVal) {
val = newVal;
document.getElementById('input').value = val;
document.getElementById('text').innerHTML = val;
}
});
document.addEventListener('keyup', function(e) {
obj.prop = e.target.value;
});