深入學習前端MVC和MVVM(二)
上一節說了後臺的MVC,現在開始講重點,前端的MVC又是一個什麼鬼。
很長一段時間我都沒有搞清楚MVC和MVVM。
一直在說ng是MVC,react和Vue是MVVM,MVVM我用過了,用過vue和react,他們的資料繫結,那麼MVC究竟是什麼樣子呢?
一個簡單MVC的實現
重點:
MVC的基礎是觀察者模式;
這裡還有一個問題,就是MVC為什麼不是23種設計模式中的一種呢?
在網上找了很多答案:
說MVC每一個M、V、C都是一種模式,即觀察者模式、策略模式、組合模式的結合;
第一步:先實現model來看看
這裡我們先看Model.js
//實際上這裡是一個觀察者模式
function Model(value) {
this._value = typeof value === 'undefined' ? '' : value;
this._listeners = [];
}
Model.prototype.set = function (value) {
var self = this;
self._value = value;
console.log(value);
// model中的值改變時,應通知註冊過的回撥函式
// 按照Javascript事件處理的一般機制,我們非同步地呼叫回撥函式
// 如果覺得setTimeout影響效能,也可以採用requestAnimationFrame
setTimeout(function () {
self._listeners.forEach(function (listener) {
listener.call(self, value);
});
});
};
Model.prototype.watch = function (listener) {
// 註冊監聽的回撥函式
console.log(listener);
this._listeners.push(listener);
};
<div id="div1"></div >
<script src="./model.js"></script>
<script>
// 邏輯程式碼:
(function() {
var model = new Model();
var div1 = document.getElementById('div1');
var setValue=function (value) {
div1.innerHTML=value;
}
// model.watch(function(value) {
// div1.innerHTML = value;
// });
model.watch(setValue)
model.set('hello, this is a div');
model.set('hello, this is a div2');
})();
</script>
上面程式碼的呼叫是這樣的:提供了一個監聽方法,Model.prototype.wacth,在例項中,監聽的就是setValue這個方法,(我把原來程式碼裡面的匿名函式註釋掉了,因為這樣便於理解)
監聽事件列隊插入 listener;
這裡事實上我們並沒有執行直接通過setValue(‘hello, this is a div’)執行,而是呼叫model的set方法執行的setValue(‘hello, this is a div’)
在model的set方法中,是遍歷監聽的事件。並將傳給set的值,傳給listener中的每一個事件。
比如我們稍微改一下:
/*view*/
<div id="div1"></div>
<div id="div2"></div>
<script src="./model.js"></script>
<script>
// 邏輯程式碼:
(function() {
var model = new Model();
var div1 = document.getElementById('div1');
var div2 = document.getElementById('div2');
var setValue=function (value) {
div1.innerHTML=value;
}
var setValue2=function (value) {
div2.innerHTML=value;
}
model.watch(setValue)
model.watch(setValue2)
model.set('hello, this is a div2');
})();
</script>
上面的程式碼model監聽了兩個事件,為div1和div2設值的setValue()和setValue2()
這裡只調用了一次set方法,但是這兩個setValue都執行了。
藉助觀察者模式,我們已經實現了在呼叫model的set方法改變其值的時候,模板也同步更新,但這樣的實現卻很彆扭,因為我們需要手動監聽model值的改變(通過watch方法)並傳入一個回撥函式,有沒有辦法讓view(一個或多個dom node)和model更簡單的繫結呢?
進一步,改進model,分離model和view的業務
上面的程式碼中view層還是有寫setValue函式,不符合view的邏輯,view應該是看不到內部的操作。因此通過bind的方式實現setValue
var setValue=function (value) {
div1.innerHTML=value;
}
通過bind的方式實現,在model.js中加上
Model.prototype.bind = function (node) {
// 將watch的邏輯和通用的回撥函式放到這裡
this.watch(function (value) {
node.innerHTML = value;
});
};
這個時候view的寫法:
就只是看到節點繫結到了model上,以及設定節點的值。
(function () {
var model = new Model();
model.bind(document.getElementById('div1'));
model.bind(document.getElementById('div2'));
model.set('this is a div');
})();
最後一步:controller
這裡一直只提到了MV,並沒有C,其實controller是一種看不見的方式在運作
這裡是controller.js的程式碼:
function Controller(callback) {
var models = {};
// 找到所有有bind屬性的元素
var views = document.querySelectorAll('[bind]');
// 將views處理為普通陣列
views = Array.prototype.slice.call(views, 0);
views.forEach(function(view) {
var modelName = view.getAttribute('bind');
// 取出或新建該元素所繫結的model
models[modelName] = models[modelName] || new Model();
// 完成該元素和指定model的繫結
models[modelName].bind(view);
});
// 呼叫controller的具體邏輯,將models傳入,方便業務處理
callback.call(this, models);
}
view層,實際上controller層完成了view和model的繫結。
<div id="div1" view="model1"></div>
<div id="div2" view="model2"></div>
new Controller(function (models) {
console.log(models);
var model1 = models.model1;
model1.set('this is a div');
});