1. 程式人生 > >深入學習前端MVC和MVVM(二)

深入學習前端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');
});