前言
演變歷史:
直接操作DOM -> MVC -> MVP -> MVVM
MVC
- M —— model 數(shù)據(jù)層& 手動(dòng)渲染模板
- V —— view 視圖層
-
C —— controller 控制層(業(yè)務(wù)邏輯)
流程:
V ——> C ——> M ——> V
- View 傳送指令 到 controller
- controller 完成業(yè)務(wù)邏輯處理后,觸發(fā) Model 改變狀態(tài)
- Model 將新的數(shù)據(jù)發(fā)送到 View, View 更新視圖
所有的通信都是單向的
見圖image.png
實(shí)現(xiàn)代碼
// html
<div id="A" onclick="A.event.change"></div>
//js
var A = new app({
//controller
controller() {
let self = this
// 綁定事件
self.event['change'] = function() {
self.model.setValue('text', '新的viewA 的值') // 觸發(fā)model數(shù)據(jù)更新
}
},
//model
model(){
this.text = 'viewA 渲染完成';
// 綁定更新回調(diào)函數(shù)
this.setValue = function(key, val){
this[key] = val // 更新新值
this.view.render(this) // 調(diào)用渲染函數(shù)
}
}
// view
view(data) {
var tpl = ''<span>{{text}}</span>'
this.render= function(tpl,data){
// 渲染視圖
。。。
}
}
})
MVP 是對(duì)MVC的一種改造,將手動(dòng)渲染步驟從Model 移到Presenter層上
- M —— model 數(shù)據(jù)層
- V —— view 視圖層
-
P —— presenter 控制層 & 手動(dòng)渲染模板
流程:
V <——> P <——> M
- 各部分直接的通信,都是雙向的
- View 與 Model 不發(fā)生聯(lián)系,相互對(duì)立
見圖
image.png

image.png
實(shí)現(xiàn)代碼
// html
<div id="A" onclick="A.event.change"></div>
//js
var A = new app({
//presenter
presenter() {
let self = this
// 綁定事件
self.event['change'] = function() {
self.model.text = "新的viewA 的值"
self.view.render(self.model)
}
},
//model
model(){
this.text = 'viewA 渲染完成';
}
// view
view(data) {
var tpl = ''<span>{{text}}</span>'
this.render= function(tpl,data){
// 渲染視圖
。。。
}
}
})
MVVM 是將Presenter 改為ViewModel ,基本與MVP模式一致
唯一區(qū)別是:它采用雙向綁定:View 的變動(dòng),自動(dòng)反映在ViewModel上,反之亦然
- M —— model 數(shù)據(jù)層
- V —— view 視圖層
-
VM —— ViewModel 依靠Directive, 修改數(shù)據(jù)& 自動(dòng)渲染模板
流程:
V <——> VM <——> M
圖
image.png
實(shí)現(xiàn)代碼
// html
<div id="A" onclick="change">
<span >{{text}}</span>
</div>
//js
new VM({
//model
model:{
text = 'viewA 渲染完成';
},
methods: {
change(){
this.text = "新的viewA 的值"
}
}
})
MVVM模式依靠Directive, 實(shí)現(xiàn)了修改數(shù)據(jù)和模板自動(dòng)渲染,解放了開發(fā)者,只需要關(guān)注View和Model,效率和性能提高,低耦合度,對(duì)立開發(fā),可復(fù)用性
數(shù)據(jù)變動(dòng)檢查方案 (Directive)
一. 手動(dòng)觸發(fā)綁定
在頁面需要改變是,手動(dòng)觸發(fā)檢測(cè),改變Model數(shù)據(jù),并掃描元素,對(duì)有標(biāo)記的元素進(jìn)行修改
let data = {
value: 'hello'
};
let directive = {
html: function (html) {
this.innerHTML = html;
},
value: function (html) {
this.setAttribute('value', value);
}
};
ViewModelSet('value', 'hello world');
function ViewModelSet(key, value) {
data[key] = value;
scan();
}
function scan() {
for (let elem of elems) {
elem.directive = [];
for (let attr of elem.attributes) {
if (attr.nodeName.indexOf('v-') >= 0) {
directive[attr.nodeName.slice(2)].call(elem, data[attr.nodeValue]);
}
}
}
}
一. 臟檢測(cè)機(jī)制 (Angrlar)
針對(duì)手動(dòng)綁定進(jìn)行優(yōu)化,只對(duì)修改到的數(shù)據(jù)進(jìn)行更新元素
function scan(elems, val) {
let list = document.querySelectorAll(`[v-bind=${val}]`); // 只掃描修改到的數(shù)據(jù)涉及的元素
for (let elem of elems) {
for (let attr of elem.attributes) {
let dataKey = elem.getAttribute('v-bind');
if (elem.directive[attr.nodeValue] !== data[dataKey]) { // 當(dāng)元素值有變時(shí),更新元素
directive[attr.nodeValue].call(elem, data[dataKey]);
elem.directive[attr.nodeValue] = data[dataKey]; // 保存元素當(dāng)前值
}
}
}
}
三. 數(shù)據(jù)劫持結(jié)合訂閱發(fā)布模式 (Vue)
使用 Object.defineProperty 對(duì)數(shù)據(jù)進(jìn)行g(shù)et 和 set 監(jiān)聽,數(shù)據(jù)變動(dòng)時(shí),通知訂閱者調(diào)用更新回調(diào)函數(shù),重新渲染視圖
詳細(xì)內(nèi)容請(qǐng)看我的文章Vue的雙向綁定原理及實(shí)現(xiàn)
四. ES6 Proxy
與方法三類似,換成ES6的寫法
let data = new Proxy({
get: function(obj, key){
return obj[key]
},
set: function(obj,key,val){
obj[key] = val
scan() // 訂閱更新回調(diào)函數(shù)
return obj[key]
}
})
參考文獻(xiàn):
https://segmentfault.com/a/1190000013464776#articleHeader3
http://www.ruanyifeng.com/blog/2015/02/mvcmvp_mvvm.html

