簡單談談js中的MVC

来源:http://www.cnblogs.com/LIUYANZUO/archive/2017/07/24/7231703.html
-Advertisement-
Play Games

MVC是什麼? MVC是一種架構模式,它將應用抽象為3個部分:模型(數據)、視圖、控制器(分發器)。 本文將用一個經典的例子todoList來展開(代碼在最後)。 一個事件發生的過程(通信單向流動): 1、用戶在視圖 V 上與應用程式交互 2、控制器 C 觸發相應的事件,要求模型 M 改變狀態(讀寫 ...


MVC是什麼?

MVC是一種架構模式,它將應用抽象為3個部分:模型(數據)、視圖、控制器(分發器)。

本文將用一個經典的例子todoList來展開(代碼在最後)。

 

一個事件發生的過程(通信單向流動):

1、用戶在視圖 V 上與應用程式交互

2、控制器 C 觸發相應的事件,要求模型 M 改變狀態(讀寫數據)

3、模型 M 將數據發送到視圖 V ,更新數據,展現給用戶

 

js傳統開發模式,大多基於事件驅動的

1、hash驅動

2、DOM事件,用來驅動視圖

3模型事件(業務模型事件和數據模型事件),用來驅動模型和模型結合

所以js中的mvc的特點是:單向流動、事件驅動

 

一)模型

模型存放應用的所有數據對象業務數據、數據校驗、增刪改查),比如,例子todoList中的store模型,存放每一條記錄與之有關的邏輯。

數據是面向對象的,當控制器請求模型讀寫數據時,模型就將數據包裝成模型實例。任何定義在這個數據模型上的函數或邏輯都可以直接被調用。在本文的例子中採用localSrorage也是類似道理的。存儲的Todos可以隨時被調用

模型不關心,不包含視圖和控制器的邏輯。它們應該是互相解耦的。這裡提一點,模型視圖的耦合顯然是違反MVC架構原則,但往往我們有時候卻因為業務關係而無法完全解耦

模型表現了領域特定的數據當一個模型有所改變的時候它會通知它的觀察者(視圖)

 

二)視圖

視圖是呈現給用戶的,是用戶交互的第一入口。它定義配置管理著每個頁面相應的模板與組件,它表現一個模型的當前狀態視圖通過觀察者模式監視模型,以獲得最新的數據,來呈現最新的頁面所以,頁面首次載入時,往往是從接收模型的數據開始。

 

三)控制器

控制器分發器),是模型和視圖之間的橋梁集中式配置和管理事件分發、模型分發、視圖分發,還用來許可權控制、異常處理等。我們的應用中往往是有多個控制器的

頁面載入完成後,控制器監聽視圖的用戶交互按鈕點擊或表單提交一旦用戶發生交互時控制器做出對視圖的選擇觸發控制器的事件處理機制去派發新的事件,通知模型更新數據(這樣就回到了第一步了)

 

Demo-todoList

最後這裡是一個用原生js寫的todoLIst,這個demo做的很簡陋,點擊輸入文字點擊確定就添加,刪除是直接點擊該行信息。

 

單獨分離開來舉例子不好講,所以在代碼中進行註釋。首先簡單理下下邊代碼的思路:

1、V層定義配置了一個顯示數據的字元串模板,同時定義一個訂閱者的回調函數render() 用於頁面更新數據。

2、C層監聽用戶的添加與刪除操作,添加是add() 函數 它執行了回調函數render,同時向M層寫入數據,通知M層改變。刪除操作同理。

3、M層是本地存儲localStorage,模擬一個存儲數據對象的後臺模型。

  1 <!DOCTYPE html>
  2 <html lang="en">
  3 <head>
  4     <meta charset="UTF-8">
  5     <title>todo</title>
  6 </head>
  7 <body>
  8 <header>
  9     <h3>待定事項</h3>
 10 </header>
 11 <main>
 12     <ul id="todoList"></ul>
 13     <input type="text" id="content">
 14     <button id="confirm">確認</button>
 15 </main>
 16 
 17 <script>
 18   (function () {
 19     const ADD_KEY = '__todoList__'
 20 
 21     const Utils = {
 22       // 模擬 Modal(實體模型)
 23       store(key, data) {
 24         if (arguments.length > 1) {
 25           return localStorage.setItem(key, JSON.stringify(data));
 26         } else {
 27           let storeData = localStorage.getItem(key);
 28           return (storeData && JSON.parse(storeData)) || []; // 這裡一定要設置初始值為 []
 29         }
 30       }
 31     }
 32 
 33     class Todo {
 34       constructor(id, text = "") {
 35         this.id = id
 36         this.text = text
 37       }
 38     }
 39 
 40     let App = {
 41       init() {
 42         // this.todos 為一個存儲json對象的數組, 是一個實例化的數據對象,可任意調用
 43         this.todos = Utils.store(ADD_KEY)
 44         this.findDom()
 45         this.bindEvent()
 46         this.render() // 初始化渲染
 47       },
 48 
 49 
 50       findDom() {
 51         this.contentBox = document.querySelector("#content")
 52         this.confirm = document.querySelector("#confirm")
 53         this.todoList = document.querySelector("#todoList")
 54         this.todoListItem = document.getElementsByTagName("li")
 55       },
 56 
 57       // 模擬 Controller (業務邏輯層)
 58       bindEvent() {
 59         this.confirm.addEventListener('click', () => {
 60           // 要求模型 M 改變狀態,add()函數是寫入數據操作
 61           this.add()
 62         }, false)
 63 
 64         this.todoList.addEventListener('click', (item) => {  // 事件委托,優化性能
 65           this.remove(item)
 66         }, false)
 67       },
 68 
 69       // 這裡勉強抽象成一個視圖吧!!!
 70       view() {
 71         let fragment = document.createDocumentFragment()   // 減少迴流次數
 72         fragment = ''
 73 
 74         for (let i = 0; i < this.todos.length; i++) { // 一次性DOM節點生成
 75           // 這裡使用拼接字元串代替視圖的模板,
 76           // *******註意模板並不是一個視圖,模板是由視圖定義配置出來的,並被其管理著*******
 77           // 模板是用一種聲明的方式指定部分甚至所有的視圖對象
 78           fragment += `<li>${this.todos[i].text}</li>`
 79         }
 80         this.todoList.innerHTML = fragment
 81       },
 82 
 83       // render()函數作為一個訂閱者的回調函數,數據的變化會反饋到模型 store
 84       // 換句話說:視圖通過觀察者模式,觀察模型 store,當模型發生改變,觸發視圖更新
 85       render() {
 86         this.view()
 87 
 88         /**
 89          * 這裡需要特別提一下,按照 MVC 原則這裡本不應該出現下麵的代碼的
 90          * 因為業務邏輯關係(我本地存儲使用的是同一個key值,再次寫入數據會覆蓋原來的數據,),
 91          * 所以必須通知模型 M 保存數據, V 層處理了不該它處理的邏輯,導致 M 與 V 耦合
 92          *
 93          * 解決辦法是:將其抽象出來編寫一個 視圖助手 helper
 94          */
 95         Utils.store(ADD_KEY, this.todos)
 96       },
 97 
 98       getItemIndex(item) {
 99         let itemIndex
100         if (item.target.tagName.toLowerCase() === 'li') {
101           let arr = Array.prototype.slice.call(this.todoListItem)
102           let index = arr.indexOf(item.target)
103           return itemIndex = index
104         }
105       },
106 
107       add(e) {
108         let id = Number(new Date())
109         let text = this.contentBox.value
110         let addTodo = new Todo(id, text)
111         this.todos.unshift(addTodo) // 模型發生改變
112         this.render()  // 當模型發生改變,觸發視圖更新
113       },
114 
115       remove(item) {
116         let index = this.getItemIndex(item)
117         this.todos.splice(index, 1)
118         this.render()
119       }
120     }
121 
122     App.init()
123   })()
124 </script>
125 </body>
126 </html>

隨著界面和邏輯的複雜,用js或者jq去控制DOM不現實的。上邊例子只是用原生js模擬mvc的思想實現過程。真正地項目往往會依賴一些封裝好的優秀庫進行高效開發。

 

mvc模式的優點

mvc編程把所有精力放在數據處理,儘可能減少對網頁元素的處理。對於一定數量功能的網頁,Mvc模式下強制規範代碼簡化減少重覆代碼,使代碼易於擴充

 

mvc模式的弊端

1、清晰的構架以代碼的複雜性為代價, 對小項目反而降低開發效率。 (如果本文的例子todoList用麵條式代碼編寫,那得多簡單啊!!!)
2、控制層和視圖層耦合,導致沒有真正分離和重用 

3、在同一業務邏輯下,如果存在多種視圖呈現,需要視圖定義配置多個模板引擎、數據解析,多次處理數據與頁面更新。代碼就充滿了各種選擇器與事件回調,隨著業務的膨脹,變得難以維護。

 

總結:其實,現在MVC在前端用得比較少了,因為它的局限性,催生了MVVM模式的流行與廣泛使用,在下篇文章我會談談我對MVVM的理解,以及為何我使用基於MVVM模式的vue框架來高效開發。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 顯式與隱式(Explicit And Implicit) 1.概念 1.1 顯式 實現的單詞Explicit意思是清楚的、明確的、詳述的。所以,顯式的“顯”是指明顯且清楚的實現,相對於介面來說,就是明顯而清楚的指定了介面的實現。對於其他的邏輯來說,顯式就是清楚且明確的指定了實現內容。 1.2 隱式 ...
  • 目錄: 基礎篇_功能各自回顧 JDBC基礎代碼回顧(使用JdbcUtils工具簡化) c3p0資料庫連接池的使用(使用JdbcUtils工具簡化) 大數據的插入(使用c3p0+JdbcUtils工具簡化) 批處理操作(使用c3p0+JdbcUtils工具簡化) 資料庫中的事務處理(使用c3p0+Jd ...
  • // 對Date的擴展,將 Date 轉化為指定格式的String // 月(M)、日(d)、小時(h)、分(m)、秒(s)、季度(q) 可以用 1-2 個占位符, // 年(y)可以用 1-4 個占位符,毫秒(S)只能用 1 個占位符(是 1-3 位的數字) Date.prototype.Form ...
  • Navigator 對象包含有關瀏覽器的信息。 註釋:沒有應用於 navigator 對象的公開標準,不過所有瀏覽器都支持該對象。 Navigator 對象屬性: 在谷歌瀏覽器中列印Navigator對象,如圖所示: ...
  • 在Vue.js中,在實例化Vue之前,它們都是以HTML的文本形式存在文本編輯器中。當實例化後將經歷創建、編譯、銷毀三個主要階段,以下是Vue.js 1.x 和 2.0 實例的生命周期的介紹。 ...
  • 技術棧。前端:Express & EJS & ES6 & Less & Gulp。後端:Express & SocketIO & MongoDB & [REST API](API.md)。部署:Linux & PM2。 ...
  • 據說公司的項目較多的用到requirejs管理依賴,所以大熊同學擠出了5分鐘休息時間學習了一下,現在分享一下。如果你想瞭解requirejs的實現原理,請繞道!如果你想瞭解requirejs的高級用法,請繞道!那麼問題來了,這篇小文還有人看嗎? ...
  • 提起路由,首先想到的就是 ASPNET MVC 裡面的路由系統 通過事先定義一組路由規則,程式運行時就能自動根據我們輸入的URL來返回相對應的頁面。前端中的路由與之類似,前端中的路由是根據你定義的路由規則來渲染不同的頁面/組件,同時也會更新地址欄的URL。本篇文章要介紹的是React中經常使用到的路 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...