• <nav id="cwumo"><code id="cwumo"></code></nav>
  • <menu id="cwumo"><strong id="cwumo"></strong></menu>

    30行代碼實現Javascript中的MVC

    時間:2024-06-08 03:15:28 JavaScript 我要投稿
    • 相關推薦

    30行代碼實現Javascript中的MVC

    一連串的名字走馬觀花式的出現和更迭,它們中一些已經漸漸淡出了大家的視野,一些還在迅速茁壯成長,一些則已經在特定的生態環境中獨當一面舍我其誰。但不論如何,MVC已經并將持續深刻地影響前端工程師們的思維方式和工作方法。

    很多講解MVC的例子都從一個具體的框架的某個概念入手,比如Backbone的collection或AngularJS中model,這當然不失為一個好辦法。但框架之所以是框架,而不是類庫(jQuery)或者工具集(Underscore),就是因為它們的背后有著眾多優秀的設計理念和最佳實踐,這些設計精髓相輔相成,環環相扣,缺一不可,要想在短時間內透過復雜的框架而看到某一種設計模式的本質并非是一件容易的事。

    這便是這篇隨筆的由來——為了幫助大家理解概念而生的原型代碼,應該越簡單越好,簡單到剛剛足以大家理解這個概念就夠了。

     

    1.MVC的基礎是觀察者模式,這是實現model和view同步的關鍵

     

    為了簡單起見,每個model實例中只包含一個primitive value值。

     

    function Model(value) {

        this._value = typeof value === 'undefined' ? '' : value;

        this._listeners = [];

    }

    Model.prototype.set = function (value) {

        var self = this;

        self._value = value;

        // model中的值改變時,應通知注冊過的回調函數

        // 按照Javascript事件處理的一般機制,我們異步地調用回調函數

        // 如果覺得setTimeout影響性能,也可以采用requestAnimationFrame

        setTimeout(function () {

            self._listeners.forEach(function (listener) {

                listener.call(self, value);

            });

        });

    };

    Model.prototype.watch = function (listener) {

        // 注冊監聽的回調函數

        this._listeners.push(listener);

    };

     

    // html代碼:

    <div id="div1"></div>

    // 邏輯代碼:

    (function () {

        var model = new Model();

        var div1 = document.getElementById('div1');

        model.watch(function (value) {

            div1.innerHTML = value;

        });

        model.set('hello, this is a div');

    })();

     

    借助觀察者模式,我們已經實現了在調用model的set方法改變其值的時候,模板也同步更新,但這樣的實現卻很別扭,因為我們需要手動監聽model值的改變(通過watch方法)并傳入一個回調函數,有沒有辦法讓view(一個或多個dom node)和model更簡單的綁定呢?

     

    2. 實現bind方法,綁定model和view

     

    Model.prototype.bind = function (node) {

        // 將watch的邏輯和通用的回調函數放到這里

        this.watch(function (value) {

            node.innerHTML = value;

        });

    };

     

    // html代碼:

    <div id="div1"></div>

    <div id="div2"></div>

    // 邏輯代碼:

    (function () {

        var model = new Model();

        model.bind(document.getElementById('div1'));

        model.bind(document.getElementById('div2'));

        model.set('this is a div');

    })();

     

    通過一個簡單的封裝,view和model之間的綁定已經初見雛形,即使需要在一個model上綁定多個view,實現起來也很輕松。注意bind是Function類prototype上的一個原生方法,不過它和MVC的關系并不緊密,筆者又實在太喜歡bind這個單詞,一語中的,言簡意賅,所以索性在這里把原生方法覆蓋了,大家可以忽略。言歸正傳,雖然綁定的復雜度降低了,這一步依然要依賴我們手動完成,有沒有可能把綁定的邏輯從業務代碼中徹底解耦呢?

     

    3. 實現controller,將綁定從邏輯代碼中解耦

     

    細心的朋友可能已經注意到,雖然講的是MVC,但是上文中卻只出現了Model類,View類不出現可以理解,畢竟HTML就是現成的View(事實上本文中從始至終也只是利用HTML作為View,javascript代碼中并沒有出現過View類),那Controller類為何也隱身了呢?別急,其實所謂的”邏輯代碼”就是一個框架邏輯(姑且將本文的原型玩具稱之為框架)和業務邏輯耦合度很高的代碼段,現在我們就來將它分解一下。

     

    如果要將綁定的邏輯交給框架完成,那么就需要告訴框架如何來完成綁定。由于JS中較難完成annotation(注解),我們可以在view中做這層標記——使用html的標簽屬性就是一個簡單有效的辦法。

     

    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);

    }

     

    // html:

    <div id="div1" bind="model1"></div>

    <div id="div2" bind="model1"></div>

    // 邏輯代碼:

    new Controller(function (models) {

        var model1 = models.model1;

        model1.set('this is a div');

    });

     

    就這么簡單嗎?就這么簡單:在Controller中完成業務邏輯并對Model進行修改,Model的變化觸發View的自動更新,怎么樣,算得上一個有模有樣的MVC吧?當然,這樣的”框架”還不足以用于生產環境,不過如果它能或多或少地幫助到大家對于MVC的理解的話,博主就非常滿足了。

     

    整理后去掉注釋的”框架”代碼:

     

    function Model(value) {

        this._value = typeof value === 'undefined' ? '' : value;

        this._listeners = [];

    }

    Model.prototype.set = function (value) {

        var self = this;

        self._value = value;

        setTimeout(function () {

            self._listeners.forEach(function (listener) {

                listener.call(self, value);

            });

        });

    };

    Model.prototype.watch = function (listener) {

        this._listeners.push(listener);

    };

    Model.prototype.bind = function (node) {

        this.watch(function (value) {

            node.innerHTML = value;

        });

    };

    function Controller(callback) {

        var models = {};

        var views = Array.prototype.slice.call(document.querySelectorAll('[bind]'), 0);

        views.forEach(function (view) {

            var modelName = view.getAttribute('bind');

            (models[modelName] = models[modelName] || new Model()).bind(view);

        });

        callback.call(this, models);

    }

     

    4. 一個簡單的例子

     

    下面請大家看一個簡單例子,如何實現電子表

     

    // html:

    <span bind="hour"></span> : <span bind="minute"></span> : <span bind="second"></span>

    // controller:

    new Controller(function (models) {

        function setTime() {

            var date = new Date();

            models.hour.set(date.getHours());

            models.minute.set(date.getMinutes());

            models.second.set(date.getSeconds());

        }

        setTime();

        setInterval(setTime, 1000);

    });

     

    可以看出,controller中只負責更新model的邏輯,和view完全解耦;而view和model的綁定是通過view中的屬性和框架中controller的初始化代碼完成的,也沒有出現在業務邏輯中;至于view的更新,也是通過框架中的觀察者模式實現的。


    【30行代碼實現Javascript中的MVC】相關文章:

    JavaScript實現網頁刷新代碼段08-07

    在Java中執行JavaScript代碼07-14

    常用排序算法之JavaScript實現代碼段06-04

    關jQuery彈出窗口簡單實現代碼-javascript編程06-07

    高效編寫JavaScript代碼的技巧08-25

    關于ASP.NET使用JavaScript顯示信息提示窗口實現原理及代碼05-09

    將php實現過濾UBB代碼09-11

    網頁程序設計之實用JavaScript代碼段09-23

    JavaScript中的with關鍵字07-24

    Javascript中typeof 用法歸納09-27

    日韩激情