僅30行代碼實現Javascript中的MVC,30行mvc

來源:互聯網
上載者:User

僅30行代碼實現Javascript中的MVC,30行mvc

從09年左右開始,MVC逐漸在前端領域大放異彩,並終於在剛剛過去的2015年隨著React Native的推出而迎來大爆發:AngularJS、EmberJS、Backbone、ReactJS、RiotJS、VueJS…… 一連串的名字走馬觀花式的出現和更迭,它們中一些已經漸漸淡出了大家的視野,一些還在迅速茁壯成長,一些則已經在特定的生態環境中獨當一面捨我其誰。但不論如何,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之間的綁定已經初見雛形,即使需要綁定多個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');});

就這麼簡單嗎?就這麼簡單。MVC的本質就是在controller中完成商務邏輯,並對model進行修改,同時model的改變引起view的自動更新,這些邏輯在上面的代碼中都有所體現,並且支援多個view、多個model。雖然不足以用於生產項目,但是希望對大家的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();    models[modelName].bind(view);  });  callback.call(this, models);}

後記:

筆者在學習flux和redux的過程中,雖然掌握了工具的使用方法,但只是知其然而不知其所以然,對ReactJS官方文檔中一直強調的 "Flux eschews MVC in favor of a unidirectional data flow" 不甚理解,始終覺得單向資料流和MVC並不衝突,不明白為什麼在ReactJS的文檔中這二者會被對立起來,有他無我,有我無他(eschew,避開)。終於下定決心,回到MVC的定義上重新研究,雖然平日工作裡大大咧咧複製粘貼,但是咱們偶爾也得任性一把,咬文嚼字一番,對吧?這樣的方式也的確協助了我對於這句話的理解,這裡可以把自己的思考分享給大家:之所以覺得MVC和flux中的單向資料流相似,可能是因為沒有區分清楚MVC和觀察者模式的關係造成的——MVC是基於觀察者模式的,flux也是,因此這種相似性的由來是觀察者模式,而不是MVC和flux本身。這樣的理解也在四人組的設計模式原著中得到了印證:"The first and perhaps best-known example of the Observer pattern appears in Smalltalk Model/View/Controller (MVC), the user interface framework in the Smalltalk environment [KP88]. MVC's Model class plays the role of Subject, while View is the base class for observers. "。

如果讀者有興趣在這樣一個原型玩具的基礎上繼續拓展,可以參考下面的一些方向:

  • 1. 實現對input類標籤的雙向繫結
  • 2. 實現對controller所控制的scope的精準控制,這裡一個controller就控制了整個dom樹
  • 3. 實現view層有關dom node隱藏/顯示、建立/銷毀的邏輯
  • 4. 整合virtual dom,增加dom diff的功能,提高渲染效率
  • 5. 提供依賴注入功能,實現控制反轉
  • 6. 對innerHTML的賦值內容進行安全檢查,防止惡意注入
  • 7. 實現model collection的邏輯,這裡每個model只有一個值
  • 8. 利用es5中的setter改變set方法的實現,使得對model的修改更加簡單
  • 9. 在view層中增加對屬性和css的控制
  • 10.支援類似AngularJS中雙大括弧的文法,只綁定部分html
  • ……

一個完善的架構要經過無數的提煉和修改,這裡只是最初最初的第一步,道路還很漫長,希望大家再接再厲。

您可能感興趣的文章:
  • 使用jQuery向asp.net Mvc傳遞複雜json資料-ModelBinder篇
  • Extjs4.1.x 架構搭建 採用Application動態按需載入MVC各模組完美實現
  • 談談關於JavaScript 中的 MVC 模式
  • MVC後台建立Json(List)前台接受並迴圈讀取執行個體
  • 教你如何在 Javascript 檔案裡使用 .Net MVC Razor 語法
  • Javascript MVC架構Backbone.js詳解
  • .Net基於MVC4 Web Api輸出Json格式執行個體
  • ASP.NET中MVC使用AJAX調用JsonResult方法並返回自訂錯誤資訊

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.