30行代碼構建javascript 的MVC模式

MVC模式是軟件工程中一種軟件架構模式,通常把軟件模式分爲三部分,模型(Model)+視圖(View)+控制器(Controller);
不少講解MVC的例子都從一個具體的框架的某個概念入手,好比Backbone的collection或AngularJS中model,這固然不失爲一個好辦法。但框架之因此是框架,而不是類庫(jQuery)或者工具集(Underscore),就是由於它們的背後有着衆多優秀的設計理念和最佳實踐,這些設計精髓相輔相成,環環相扣,缺一不可,要想在短期內透過複雜的框架而看到某一種設計模式的本質並不是是一件容易的事。
這即是這篇隨筆的由來——爲了幫助你們理解概念而生的原型代碼,應該越簡單越好,簡單到剛剛足以你們理解這個概念就夠了。
1.MVC的基礎是觀察者模式,這是實現model和view同步的關鍵
爲了簡單起見,每一個model實例中只包含一個primitive value值。
 1 function Model(value) {
 2     this._value = typeof value === 'undefined' ? '' : value;
 3     this._listeners = [];
 4 }
 5 Model.prototype.set = function (value) {
 6     var self = this;
 7     self._value = value;
 8     // model中的值改變時,應通知註冊過的回調函數
 9     // 按照Javascript事件處理的通常機制,咱們異步地調用回調函數
10     // 若是以爲setTimeout影響性能,也能夠採用requestAnimationFrame
11     setTimeout(function () {
12         self._listeners.forEach(function (listener) {
13             listener.call(self, value);
14         });
15     });
16 };
17 Model.prototype.watch = function (listener) {
18     // 註冊監聽的回調函數
19     this._listeners.push(listener);
20 };
21   
22 // html代碼:
23 <div id="div1"></div>
24 // 邏輯代碼:
25 (function () {
26     var model = new Model();
27     var div1 = document.getElementById('div1');
28     model.watch(function (value) {
29         div1.innerHTML = value;
30     });
31     model.set('hello, this is a div');
32 })();
藉助觀察者模式,咱們已經實現了在調用model的set方法改變其值的時候,模板也同步更新,但這樣的實現卻很彆扭,由於咱們須要手動監聽model值的改變(經過watch方法)並傳入一個回調函數,有沒有辦法讓view(一個或多個dom node)和model更簡單的綁定呢?
 
2. 實現bind方法,綁定model和view
 1 Model.prototype.bind = function (node) {
 2     // 將watch的邏輯和通用的回調函數放到這裏
 3     this.watch(function (value) {
 4         node.innerHTML = value;
 5     });
 6 };
 7   
 8 // html代碼:
 9 <div id="div1"></div>
10 <div id="div2"></div>
11 // 邏輯代碼:
12 (function () {
13     var model = new Model();
14     model.bind(document.getElementById('div1'));
15     model.bind(document.getElementById('div2'));
16     model.set('this is a div');
17 })();
經過一個簡單的封裝,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的標籤屬性就是一個簡單有效的辦法。
 1 function Controller(callback) {
 2     var models = {};
 3     // 找到全部有bind屬性的元素
 4     var views = document.querySelectorAll('[bind]');
 5     // 將views處理爲普通數組
 6     views = Array.prototype.slice.call(views, 0);
 7     views.forEach(function (view) {
 8         var modelName = view.getAttribute('bind');
 9         // 取出或新建該元素所綁定的model
10         models[modelName] = models[modelName] || new Model();
11         // 完成該元素和指定model的綁定
12         models[modelName].bind(view);
13     });
14     // 調用controller的具體邏輯,將models傳入,方便業務處理
15     callback.call(this, models);
16 }
17   
18 // html:
19 <div id="div1" bind="model1"></div>
20 <div id="div2" bind="model1"></div>
21 // 邏輯代碼:
22 new Controller(function (models) {
23     var model1 = models.model1;
24     model1.set('this is a div');
25 });
就這麼簡單嗎?就這麼簡單:在Controller中完成業務邏輯並對Model進行修改,Model的變化觸發View的自動更新,怎麼樣,算得上一個有模有樣的MVC吧?固然,這樣的」框架」還不足以用於生產環境,不過若是它能或多或少地幫助到你們對於MVC的理解的話,博主就很是知足了。
 
整理後去掉註釋的」框架」代碼:
 1 function Model(value) {
 2     this._value = typeof value === 'undefined' ? '' : value;
 3     this._listeners = [];
 4 }
 5 Model.prototype.set = function (value) {
 6     var self = this;
 7     self._value = value;
 8     setTimeout(function () {
 9         self._listeners.forEach(function (listener) {
10             listener.call(self, value);
11         });
12     });
13 };
14 Model.prototype.watch = function (listener) {
15     this._listeners.push(listener);
16 };
17 Model.prototype.bind = function (node) {
18     this.watch(function (value) {
19         node.innerHTML = value;
20     });
21 };
22 function Controller(callback) {
23     var models = {};
24     var views = Array.prototype.slice.call(document.querySelectorAll('[bind]'), 0);
25     views.forEach(function (view) {
26         var modelName = view.getAttribute('bind');
27         (models[modelName] = models[modelName] || new Model()).bind(view);
28     });
29     callback.call(this, models);[mw_shl_code=applescript,true]<span bind="hour"></span> : <span bind="minute"></span> : <span bind="second"></span>
30 // controller:
31 new Controller(function (models) {
32     function setTime() {
33         var date = new Date();
34         models.hour.set(date.getHours());
35         models.minute.set(date.getMinutes());
36         models.second.set(date.getSeconds());
37     }
38     setTime();
39     setInterval(setTime, 1000);
40 });
 1 function Model(value) {
 2     this._value = typeof value === 'undefined' ? '' : value;
 3     this._listeners = [];
 4 }
 5 Model.prototype.set = function (value) {
 6     var self = this;
 7     self._value = value;
 8     setTimeout(function () {
 9         self._listeners.forEach(function (listener) {
10             listener.call(self, value);
11         });
12     });
13 };
14 Model.prototype.watch = function (listener) {
15     this._listeners.push(listener);
16 };
17 Model.prototype.bind = function (node) {
18     this.watch(function (value) {
19         node.innerHTML = value;
20     });
21 };
22 function Controller(callback) {
23     var models = {};
24     var views = Array.prototype.slice.call(document.querySelectorAll('[bind]'), 0);
25     views.forEach(function (view) {
26         var modelName = view.getAttribute('bind');
27         (models[modelName] = models[modelName] || new Model()).bind(view);
28     });
29     callback.call(this, models);[mw_shl_code=applescript,true]<span bind="hour"></span> : <span bind="minute"></span> : <span bind="second"></span>
30 // controller:
31 new Controller(function (models) {
32     function setTime() {
33         var date = new Date();
34         models.hour.set(date.getHours());
35         models.minute.set(date.getMinutes());
36         models.second.set(date.getSeconds());
37     }
38     setTime();
39     setInterval(setTime, 1000);
40 });
4. 一個簡單的例子
下面請你們看一個簡單例子,如何實現電子錶
// html:
 
能夠看出,controller中只負責更新model的邏輯,和view徹底解耦;而view和model的綁定是經過view中的屬性和框架中controller的初始化代碼完成的,也沒有出如今業務邏輯中;至於view的更新,也是經過框架中的觀察者模式實現的。
相關文章
相關標籤/搜索