僅30行程式碼實作Javascript中的MVC_javascript技巧
從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
- ……
一個完善的框架要經過無數的提煉和修改,這裡只是最初最初的第一步,道路還很漫長,希望大家再接再厲。

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

如何使用WebSocket和JavaScript實現線上語音辨識系統引言:隨著科技的不斷發展,語音辨識技術已成為了人工智慧領域的重要組成部分。而基於WebSocket和JavaScript實現的線上語音辨識系統,具備了低延遲、即時性和跨平台的特點,成為了廣泛應用的解決方案。本文將介紹如何使用WebSocket和JavaScript來實現線上語音辨識系

WebSocket與JavaScript:實現即時監控系統的關鍵技術引言:隨著互聯網技術的快速發展,即時監控系統在各個領域中得到了廣泛的應用。而實現即時監控的關鍵技術之一就是WebSocket與JavaScript的結合使用。本文將介紹WebSocket與JavaScript在即時監控系統中的應用,並給出程式碼範例,詳細解釋其實作原理。一、WebSocket技

如何利用JavaScript和WebSocket實現即時線上點餐系統介紹:隨著網路的普及和技術的進步,越來越多的餐廳開始提供線上點餐服務。為了實現即時線上點餐系統,我們可以利用JavaScript和WebSocket技術。 WebSocket是一種基於TCP協定的全雙工通訊協議,可實現客戶端與伺服器的即時雙向通訊。在即時線上點餐系統中,當使用者選擇菜餚並下訂單

如何使用WebSocket和JavaScript實現線上預約系統在當今數位化的時代,越來越多的業務和服務都需要提供線上預約功能。而實現一個高效、即時的線上預約系統是至關重要的。本文將介紹如何使用WebSocket和JavaScript來實作一個線上預約系統,並提供具體的程式碼範例。一、什麼是WebSocketWebSocket是一種在單一TCP連線上進行全雙工

JavaScript和WebSocket:打造高效的即時天氣預報系統引言:如今,天氣預報的準確性對於日常生活以及決策制定具有重要意義。隨著技術的發展,我們可以透過即時獲取天氣數據來提供更準確可靠的天氣預報。在本文中,我們將學習如何使用JavaScript和WebSocket技術,來建立一個高效的即時天氣預報系統。本文將透過具體的程式碼範例來展示實現的過程。 We

引言在當今快速發展的數位世界中,建立健壯、靈活且可維護的WEB應用程式至關重要。 PHPmvc架構提供了實現這一目標的理想解決方案。 MVC(模型-視圖-控制器)是一種廣泛使用的設計模式,可將應用程式的各個方面分離為獨立的元件。 MVC架構的基礎MVC架構的核心原理是分離關注點:模型:封裝應用程式的資料和業務邏輯。視圖:負責呈現資料並處理使用者互動。控制器:協調模型和視圖之間的交互,管理使用者請求和業務邏輯。 PHPMVC架構phpMVC架構遵循傳統MVC模式,但也引進了語言特定的功能。以下是PHPMVC

JavaScript教學:如何取得HTTP狀態碼,需要具體程式碼範例前言:在Web開發中,經常會涉及到與伺服器進行資料互動的場景。在與伺服器進行通訊時,我們經常需要取得傳回的HTTP狀態碼來判斷操作是否成功,並根據不同的狀態碼來進行對應的處理。本篇文章將教你如何使用JavaScript來取得HTTP狀態碼,並提供一些實用的程式碼範例。使用XMLHttpRequest

用法:在JavaScript中,insertBefore()方法用於在DOM樹中插入一個新的節點。這個方法需要兩個參數:要插入的新節點和參考節點(即新節點將要插入的位置的節點)。
