前端的MVC,近幾年一直很火,你們也都紛紛討論着,因而乎,抽空總結一下這個知識點。看了些文章,結合實踐略做總結並發表一下本身的見解。 javascript
最初接觸MVC是後端Java的MVC架構,用一張圖來表示之——css
MVC是一種設計模式,它將應用劃分爲3個部分:數據(模型)、展示層(視圖)和用戶交互(控制器)。換句話說,一個事件的發生是這樣的過程:
1. 用戶和應用產生交互。
2. 控制器的事件處理器被觸發。
3. 控制器從模型中請求數據,並將其交給視圖。
4. 視圖將數據呈現給用戶。
咱們不用類庫或框架就能夠實現這種MVC架構模式。關鍵是要將MVC的每部分按照職責進行劃分,將代碼清晰地分割爲若干部分,並保持良好的解耦。這樣能夠對每一個部分進行獨立開發、測試和維護。
模型用來存放應用的全部數據對象。好比,可能有一個User模型,用以存放用戶列表、他們的屬性及全部與模型有關的邏輯。
模型沒必要知道視圖和控制器的邏輯。任何事件處理代碼、視圖模板,以及那些和模型無關的邏輯都應當隔離在模型以外。
將模型的代碼和視圖的代碼混在一塊兒,是違反MVC架構原則的。模型是最應該從你的應用中解耦出來的部分。
當控制器從服務器抓取數據或建立新的記錄時,它就將數據包裝成模型實例。也就是說,咱們的數據是面向對象的,任何定義在這個數據模型上的函數或邏輯均可以直接被調用。html
視圖層是呈現給用戶的,用戶與之產生交互。在JavaScript應用中,視圖大都是由HTML、CSS、JavaScript模板組成的。除了模板中簡單的條件語句以外,視圖不該當包含任何其餘邏輯。
將邏輯混入視圖之中是編程的大忌,這並非說MVC不容許包含視覺呈現相關的邏輯,只要這部分邏輯沒有定義在視圖以內便可。咱們將視覺呈現邏輯歸類爲「視圖助手」(helper):和視圖相關的獨立的小工具函數。
來看下面的例子,騎在視圖中包含了邏輯,這是一個範例,平時不該當這樣作:前端<div> <script> function formatDate(date) { /* ... */ } </script> ${ formateDate(this.date) } </div>在這段代碼中,咱們把formatDate()函數直接插入視圖中,這違反了MVC的原則,結果致使標籤看上去像大雜燴同樣不可維護。能夠將視覺呈現邏輯剝離出來放入試圖助手中,正以下面的代碼就避免了這個問題,可讓這個應用的結構知足MVC。java
// helper.js var helper = {}; helper.formateDate(date) { /* ... */ }; // template.html <div> ${ helper.formate(this.date) } </div>此外,全部視覺呈現邏輯都包含在helper變量中,這是一個命名空間,能夠防止衝突並保持代碼清晰、可擴展。jquery
控制器是模型和視圖之間的紐帶。控制器從視圖獲取事件和輸入,對它們(極可能包含模型)進行處理,並相應地更新視圖。當頁面加載時,控制器會給視圖添加事件監聽,好比監聽表單提交或按鈕點擊。而後,當用戶和你的應用產生交互時,控制器中的事件觸發器就開始工做了。
咱們用簡單的jQuery代碼來實現控制器——gitvar Controller = {}; (Controller.users = function($) { var nameClick = function() { /* ... */ }; // 在頁面加載時綁定事件監聽 $(function() { $('#view .name').click(nameClick); }); })(jQuery);
<section id="todoapp"> <header id="header"> <h1>todos</h1> <input id="new-todo" placeholder="What needs to be done?" autofocus> </header> <section id="main"> <input id="toggle-all" type="checkbox"> <label for="toggle-all">Mark all as complete</label> <ul id="todo-list"></ul> </section> <footer id="footer"> <span id="todo-count"><strong>0</strong> item left</span> <button id="clear-completed">Clear completed</button> </footer> </section> <footer id="info"> <p>Double-click to edit a todo</p> <p>Created by <a href="http://github.com/sindresorhus">Sindre Sorhus</a></p> <p>Part of <a href="http://todomvc.com">TodoMVC</a></p> </footer> <!-- ************************************* template begin *********************************** --> <!-- 針對模型的模板 --> <script id="todo-template" type="text/x-handlebars-template"> <!-- 這裏對todo模型數組進行迭代循環 --> {{#this}} <!-- 會看到,這裏具備簡單的if語句,即這裏具有顯示邏輯 --> <li {{#if completed}}class="completed"{{/if}} data-id="{{id}}"> <div class="view"> <input class="toggle" type="checkbox" {{#if completed}}checked{{/if}}> <label>{{title}}</label> <button class="destroy"></button> </div> <input class="edit" value="{{title}}"> </li> {{/this}} </script> <!-- /針對模型的模板 --> <!-- footer模板,記錄還剩下多少沒有完成等 --> <script id="footer-template" type="text/x-handlebars-template"> <span id="todo-count"><strong>{{activeTodoCount}}</strong> {{activeTodoWord}} left</span> {{#if completedTodos}} <button id="clear-completed">Clear completed ({{completedTodos}})</button> {{/if}} </script> <!-- /footer模板 --> <!-- ************************************* template end *********************************** --> <script src="js/base/base.js"></script> <script src="js/lib/jquery.js"></script> <script src="js/lib/handlebars.js"></script> <!-- app begin --> <script src="js/app.js"></script>
2> app.jsgithub
jQuery(function() { 'use strict'; // 這裏是一些工具函數的抽取,包括 // 1.ID生成器 // 2.顯示格式化 // 3.localStorage存儲 var Utils = { uuid : function() { /*jshint bitwise:false */ var i, random; var uuid = ''; for ( i = 0; i < 32; i++) { random = Math.random() * 16 | 0; if (i === 8 || i === 12 || i === 16 || i === 20) { uuid += '-'; } uuid += (i === 12 ? 4 : (i === 16 ? (random & 3 | 8) : random)).toString(16); } return uuid; }, pluralize : function(count, word) { return count === 1 ? word : word + 's'; }, store : function(namespace, data) { if (arguments.length > 1) { return localStorage.setItem(namespace, JSON.stringify(data)); } else { var store = localStorage.getItem(namespace); return (store && JSON.parse(store)) || []; } } }; var Todo = function(id, title, completed) { this.id = id; this.title = title; this.completed = completed; } var App = { init: function() { this.ENTER_KEY = 13; this.todos = Utils.store('todos-jquery'); this.cacheElements(); this.bindEvents(); }, // 這裏是緩存一些必要的dom節點,提升性能 cacheElements: function() { this.todoTemplate = Handlebars.compile($('#todo-template').html()); this.footerTemplate = Handlebars.compile($('#footer-template').html()); this.$todoApp = $('#todoapp'); this.$header = this.$todoApp.find('#header'); this.$main = this.$todoApp.find('#main'); this.$footer = this.$todoApp.find('#footer'); this.$newTodo = this.$header.find('#new-todo'); this.$toggleAll = this.$main.find('#toggle-all'); this.$todoList = this.$main.find('#todo-list'); this.$count = this.$footer.find('#todo-count'); this.$clearBtn = this.$footer.find('#clear-completed'); }, // 模擬Controller實現:全部的事件監聽在這裏綁定 bindEvents: function() { var list = this.$todoList; this.$newTodo.on('keyup', this.create); this.$toggleAll.on('change', this.toggleAll); this.$footer.on('click', '#clear-completed', this.destroyCompleted); list.on('change', '.toggle', this.toggle); list.on('dblclick', 'label', this.edit); list.on('keypress', '.edit', this.blurOnEnter); list.on('blur', '.edit', this.update); list.on('click', '.destroy', this.destroy); }, // 渲染記錄列表:當模型數據發生改變的時候,對應的事件處理程序調用該方法,從而實現對應DOM的從新渲染 render: function() { this.$todoList.html(this.todoTemplate(this.todos)); this.$main.toggle(!!this.todos.length); this.$toggleAll.prop('checked', !this.activeTodoCount()); this.renderFooter(); Utils.store('todos-jquery', this.todos); }, // 渲染底部 renderFooter: function () { var todoCount = this.todos.length; var activeTodoCount = this.activeTodoCount(); var footer = { activeTodoCount: activeTodoCount, activeTodoWord: Utils.pluralize(activeTodoCount, 'item'), completedTodos: todoCount - activeTodoCount }; this.$footer.toggle(!!todoCount); this.$footer.html(this.footerTemplate(footer)); }, // 建立記錄 create: function (e) { var $input = $(this); var val = $.trim($input.val()); if (e.which !== App.ENTER_KEY || !val) { return; } App.todos.push({ id: Utils.uuid(), title: val, completed: false }); // 記錄添加後,通知從新渲染頁面 App.render(); }, // 其餘業務邏輯函數 edit: function() {}, destroy: function() {} /* ... */ } App.init(); });
1.維護的model是todo實例的列表,這樣,咱們對增長記錄、刪改某一條記錄,都要從新渲染整個列表,這樣,致使性能的拙劣行。固然,改進的方式是對每個實例進行對應dom的綁定。
2.這裏的View中,咱們看到其中參雜了一些顯示邏輯,顯然,我提倡這樣去作,而非在js中去控制業務邏輯。然而,咱們在實際開發的過程中,咱們必然涉及到複雜的顯示邏輯,這樣,咱們能夠向以前所說的那樣,利用單獨編寫顯示邏輯helper,這與MVC的設計思想並不違背,確保高維護性及擴展性。
3.這裏有關模型todos的業務邏輯,並無嚴格抽象出來,而是寫入對應的事件當中。
接下來,看看其餘優秀的框架如何去作的。ajax
3、前端MVC框架express
相信你們都聽過MVC、MVP、MVVM了,三者的簡單定義——
(1)MVC: 模型-視圖-控制器(Model View Controller)
(2)MVP: 模型-視圖-表現類(Model-View-Presenter)
(3)MVVM:模型-視圖-視圖模型(Model-View-ViewModel)
它們三者的發展過程是MVC->MVP->MVVM,咱們分別來看這三者——
1> Ember.js(MVC)
先看看項目總體文件架構——
會發現,主要是有controller、model、router,先引入index.html中的模板(一樣使用的是Handlebars)——
<script type="text/x-handlebars" data-template-name="todos"> <section id="todoapp"> <header id="header"> <h1>todos</h1> <!-- 這裏的action屬性指定了對應的TodosController中的createTodo方法 --> {{input id="new-todo" type="text" value=newTitle action="createTodo" placeholder="What needs to be done?"}} </header> {{#if length}} <section id="main"> <ul id="todo-list"> {{#each filteredTodos itemController="todo"}} <li {{bind-attr class="isCompleted:completed isEditing:editing"}}> {{#if isEditing}} {{edit-todo class="edit" value=bufferedTitle focus-out="doneEditing" insert-newline="doneEditing" escape-press="cancelEditing"}} {{else}} {{input type="checkbox" class="toggle" checked=isCompleted}} <label {{action "editTodo" on="doubleClick"}}>{{title}}</label> <button {{action "removeTodo"}} class="destroy"></button> {{/if}} </li> {{/each}} </ul> {{input type="checkbox" id="toggle-all" checked=allAreDone}} </section> <footer id="footer"> <span id="todo-count">{{{remainingFormatted}}}</span> <ul id="filters"> <li> {{#link-to "todos.index" activeClass="selected"}}All{{/link-to}} </li> <li> {{#link-to "todos.active" activeClass="selected"}}Active{{/link-to}} </li> <li> {{#link-to "todos.completed" activeClass="selected"}}Completed{{/link-to}} </li> </ul> {{#if hasCompleted}} <button id="clear-completed" {{action "clearCompleted"}}> Clear completed ({{completed}}) </button> {{/if}} </footer> {{/if}} </section> <footer id="info"> <p>Double-click to edit a todo</p> <p> Created by <a href="http://github.com/tomdale">Tom Dale</a>, <a href="http://github.com/addyosmani">Addy Osmani</a> </p> <p>Part of <a href="http://todomvc.com">TodoMVC</a></p> </footer> </script>
會發現,模板代碼添加了一些晦澀的屬性標籤。對於Ember.js的使用,咱們須要建立一個Ember應用程序實例(app.js文件中)——
window.Todos = Ember.Application.create();
Todos.Router.map(function () { this.resource('todos', { path: '/' }, function () { this.route('active'); this.route('completed'); }); }); // 這裏進行了硬綁定,即對應的模板名字爲data-template-name="todos" Todos.TodosRoute = Ember.Route.extend({ model: function () { // 顯示設定該路由的的model數據 // return this.store.find('todo'); return [{ id: 1, title: 'todo1', compeled: false }]; } }); // 下面定義了三個子路由 // #/index Todos.TodosIndexRoute = Ember.Route.extend({ setupController: function () { // 顯示定義對應的controller程序 this.controllerFor('todos').set('filteredTodos', this.modelFor('todos')); } }); // #/active Todos.TodosActiveRoute = Ember.Route.extend({ setupController: function () { var todos = this.store.filter('todo', function (todo) { return !todo.get('isCompleted'); }); this.controllerFor('todos').set('filteredTodos', todos); } }); // #/completed Todos.TodosCompletedRoute = Ember.Route.extend({ setupController: function () { var todos = this.store.filter('todo', function (todo) { return todo.get('isCompleted'); }); this.controllerFor('todos').set('filteredTodos', todos); } });
1. 模板文件的模板名稱data-template-name="todos"對應的路由模板即是Todos.TodosRoute;
2. 對該路由顯示指定對應模板的數據模型。固然對這裏的數據模型(即上面的model屬性)一樣進行了硬綁定(即對應的todo.js)——
Todos.todo = DS.Model.extend({ title: DS.attr('string'), isCompleted: DS.attr('boolean'), saveWhenCompletedChanged: function() { this.save(); }.observes('isCompleted') });
3. 對該路由一樣可以指定對應的controller(上面的setController屬性)。這裏主要偵聽對hash改變,對數據進行過濾操做。
下面咱們看一看對Controller的定義,固然存在必定的硬綁定(潛規則)——todos-controller.js
Todos.TodosController = Ember.ArrayController.extend({ // 針對model集合的的交互在這裏定義 actions: { // 該方法的調用時在對應的dom節點中進行綁定,即對應模板中的下列語句 // {{input id="new-todo" type="text" value=newTitle action="createTodo" placeholder="What needs to be done?"}} createTodo: function() { var title, todo; title = this.get('newTitle').trim(); if (!title) { return; } todo = { title: title, isCompleted: false }; todo.save(); this.set('newTitle', ''); }, /* ... */ }, // 如下主要定義顯示邏輯 remaining: function () { return this.filterProperty('isCompleted', false).get('length'); }.property('@each.isCompleted'), // 對應的dom調用時<span id="todo-count">{{{remainingFormatted}}}</span> remainingFormatted: function () { var remaining = this.get('remaining'); var plural = remaining === 1 ? 'item' : 'items'; return '<strong>%@</strong> %@ left'.fmt(remaining, plural); }.property('remaining'), /* ... */ });
會發現上面的這個controller是針對model集合的,對單條model記錄的controller,放在todo-controller.js文件中——
Todos.TodoController = Ember.ObjectController.extend({ isEditing: false, // 緩存title bufferedTitle: Ember.computed.oneWay('title'), // 這裏包含了對單條記錄的全部增刪改查的操做 actions: { editTodo: function() { this.set('isEditing', true); }, doneEditing: function() { var bufferedTitle = this.get('bufferedTitle').trim(); if (Ember.isEmpty(bufferedTitle)) { Ember.run.debounce(this, this.send, 'removeTodo', 0); } else { var todo = this.get('model'); todo.set('title', bufferedTitle); todo.save(); } this.set('bufferedTitle', bufferedTitle); this.set('isEditing', false); }, cancelEditing: function() { this.set('bufferedTitle', this.get('title')); this.set('Editing', false); }, removeTodo: function() { var todo = this.get('model'); todo.deleteRecord(); todo.save(); } } });
對這些方法的調用,看一看對應的模板文件就知道了——
<ul id="todo-list"> {{#each filteredTodos itemController="todo"}} <li {{bind-attr class="isCompleted:completed isEditing:editing"}}> {{#if isEditing}} {{edit-todo class="edit" value=bufferedTitle focus-out="doneEditing" insert-newline="doneEditing" escape-press="cancelEditing"}} {{else}} {{input type="checkbox" class="toggle" checked=isCompleted}} <label {{action "editTodo" on="doubleClick"}}>{{title}}</label> <button {{action "removeTodo"}} class="destroy"></button> {{/if}} </li> {{/each}} </ul>
會發現,紅色標註的部分,正是咱們在todo-controler.js中定義的事件。還會發現,Ember.js封裝了一些事件屬性,如——
focus-out insert-newline escape-press doubleClick
到這兒,Ember.js的內容就簡單介紹完了,總結一下——
1. 程序的加載入口是rounter(即app.TemplatenameRouter),來指定對應的model及controller。路由是負責顯示模板,加載數據,以及管理應用程序的狀態。
2. 程序的交互入口是controller,這裏麪包含兩個類型的controller,一個是對應model集合的controller,一個是對應model的controller。二者各司其職,增長了代碼的可維護性。
Ember.js是典型的MVC(這裏有別於MVP、MVVM的設計模式類)框架,還有一個比較典型的MVC框架即是Angular.js,和Ember.js的設計思想大體相同。
從Ember.js的應用,咱們能夠理解MVC的特色——MVC的View直接與Model打交道,Controller僅僅起一個「橋樑」做用,它負責把View的請求轉發給Model,再負責把Model處理結束的消息通知View。Controller就是一個消息分發器。不傳遞數據(業務結果),Controller是用來解耦View和Model的,具體一點說,就是爲了讓UI與邏輯分離(界面與代碼分離)。
2>Backbone.js(MVP)
依舊先看一下文件架構——
相對於Ember.js和Angular.js,它的模板比較清爽——
<script type="text/template" id="item-template"> <div class="view"> <input class="toggle" type="checkbox" <%= completed ? 'checked' : '' %>> <label><%- title %></label> <button class="destroy"></button> </div> <input class="edit" value="<%- title %>"> </script> <script type="text/template" id="stats-template"> <span id="todo-count"> <strong><%= remaining %></strong><%= remaining === 1 ? 'item' : 'items' %> left </span> <ul id="filters"> <li> <a class="selected" href="#/">All</a> </li> <li> <a href="#/active">Active</a> </li> <li> <a href="#/completed">Completed</a> </li> </ul> <% if (completed) { %> <button id="clear-completed">Clear completed (<%= completed %>)</button> <% } %> </script>
這是因爲添加了Presenter的緣由,事件的綁定及頁面view的變化,所有由Presenter去作。
這裏存在一個model集合的概念,即這裏的collection.js——
(function() { 'use strict'; var Todos = Backbone.Collection.extend({ model: app.Todo, localStorage: new Backbone.LocalStorage('todos-backbone'), // Filter down the list of all todo items that are finished. completed: function () { return this.filter(function (todo) { return todo.get('completed'); }); }, // Filter down the list to only todo items that are still not finished. remaining: function () { return this.without.apply(this, this.completed()); }, nextOrder: function() { if (this.length === 0) { return 1; } return this.last().get('order') + 1; }, // comparator: function(todo) { return todo.get('order'); } }); app.todos = new Todos(); })();
app-view.js生成應用的一個Presenter實例(new AppView()),並由該實例來綁定事件,並控制集合todos的變化(用戶經過view產生交互來觸發),一旦todos發生變化,來觸發對應的view變化。一樣的,這裏的todo-view.js乾的是一樣一件事,只不過針對的是model單個對象。
從Backbone.js的應用,咱們能夠理解MVP的特色——Presenter直接調用Model的接口方法,當Model中的數據發生改變,通知Presenter進行對應的View改變。從而使得View再也不與Model產生交互。
3> Knockout.js(MVVM)
先看看它的頁面——
<section id="todoapp" data-bind=""> <header id="header"> <h1>todos</h1> <input id="new-todo" data-bind="value: current, valueUpdate: 'afterkeydown', enterKey: add" placeholder="What needs to be done?" autofocus> </header> <section id="main" data-bind="visible: todos().length"> <input id="toggle-all" data-bind="checked: allCompleted" type="checkbox"> <label for="toggle-all">Mark all as complete</label> <ul id="todo-list" data-bind="foreach: filteredTodos"> <li data-bind="css: { completed: completed, editing: editing }"> <div class="view"> <input class="toggle" data-bind="checked: completed" type="checkbox"> <label data-bind="text: title, event: { dblclick: $root.editItem }"></label> <button class="destroy" data-bind="click: $root.remove"></button> </div> <input class="edit" data-bind="value: title, valueUpdate: 'afterkeydown', enterKey: $root.saveEditing, escapeKey: $root.cancelEditing, selectAndFocus: editing, event: { blur: $root.stopEditing }"> </li> </ul> </section> <footer id="footer" data-bind="visible: completedCount() || remainingCount()"> <span id="todo-count"> <strong data-bind="text: remainingCount">0</strong> <span data-bind="text: getLabel(remainingCount)"></span> left </span> <ul id="filters"> <li> <a data-bind="css: { selected: showMode() == 'all' }" href="#/all">All</a> </li> <li> <a data-bind="css: { selected: showMode() == 'active' }" href="#/active">Active</a> </li> <li> <a data-bind="css: { selected: showMode() == 'completed' }" href="#/completed">Completed</a> </li> </ul> <button id="clear-completed" data-bind="visible: completedCount, click: removeCompleted"> Clear completed (<span data-bind="text: completedCount"></span>) </button> </footer> </section> <script src="js/base/base.js"></script> <script src="js/lib/knockout.js"></script> <script src="js/app.js"></script>
會發現不少data-bind屬性,先無論它,咱們在看看ViewModel的定義——
// 針對view來建立ViewModel var ViewModel = function (todos) { // map array of passed in todos to an observableArray of Todo objects this.todos = ko.observableArray(todos.map(function (todo) { return new Todo(todo.title, todo.completed); })); // store the new todo value being entered this.current = ko.observable(); this.showMode = ko.observable('all'); this.filteredTodos = ko.computed(function () { switch (this.showMode()) { case 'active': return this.todos().filter(function (todo) { return !todo.completed(); }); case 'completed': return this.todos().filter(function (todo) { return todo.completed(); }); default: return this.todos(); } }.bind(this)); // add a new todo, when enter key is pressed this.add = function () { var current = this.current().trim(); if (current) { this.todos.push(new Todo(current)); this.current(''); } }; // remove a single todo this.remove = function (todo) { this.todos.remove(todo); }.bind(this); // remove all completed todos this.removeCompleted = function () { this.todos.remove(function (todo) { return todo.completed(); }); }.bind(this); // edit an item this.editItem = function (item) { item.editing(true); item.previousTitle = item.title(); }.bind(this); // stop editing an item. Remove the item, if it is now empty this.saveEditing = function (item) { item.editing(false); var title = item.title(); var trimmedTitle = title.trim(); // Observable value changes are not triggered if they're consisting of whitespaces only // Therefore we've to compare untrimmed version with a trimmed one to chech whether anything changed // And if yes, we've to set the new value manually if (title !== trimmedTitle) { item.title(trimmedTitle); } if (!trimmedTitle) { this.remove(item); } }.bind(this); // cancel editing an item and revert to the previous content this.cancelEditing = function (item) { item.editing(false); item.title(item.previousTitle); }.bind(this); // count of all completed todos this.completedCount = ko.computed(function () { return this.todos().filter(function (todo) { return todo.completed(); }).length; }.bind(this)); // count of todos that are not complete this.remainingCount = ko.computed(function () { return this.todos().length - this.completedCount(); }.bind(this)); // writeable computed observable to handle marking all complete/incomplete this.allCompleted = ko.computed({ //always return true/false based on the done flag of all todos read: function () { return !this.remainingCount(); }.bind(this), // set all todos to the written value (true/false) write: function (newValue) { this.todos().forEach(function (todo) { // set even if value is the same, as subscribers are not notified in that case todo.completed(newValue); }); }.bind(this) }); // helper function to keep expressions out of markup this.getLabel = function (count) { return ko.utils.unwrapObservable(count) === 1 ? 'item' : 'items'; }.bind(this); // internal computed observable that fires whenever anything changes in our todos ko.computed(function () { // store a clean copy to local storage, which also creates a dependency on the observableArray and all observables in each item localStorage.setItem('todos-knockoutjs', ko.toJSON(this.todos)); }.bind(this)).extend({ throttle: 500 }); // save at most twice per second };
會發現,視圖View中的data-bind屬性值正是ViewModel實例的對應方法,這彷佛看起來很像是視圖助手helper要作的事情。其實否則,這裏的ViewModel,顧名思義,是對View的一次抽象,即對View再提取其對應的模型。
MVVM的特色以下——
1. ViewModel是model和View的中間接口
2. ViewMode提供View與Model數據之間的命令,即這裏的data-bind的值,ViewModel中的方法
3. UI的渲染均由ViewModel經過命令來控制
4、前端MVC模式與傳統開發模式的對比
傳統的開發模式,大多基於事件驅動的編碼組織,舉個例子——
$('#update').click(function(e) { // 1.事件處理程序 e.preventDefault(); // 2.獲取對應的model的屬性值 var title = $('#text').val(); // 3.調用業務邏輯 $.ajax({ url : '/xxx', type : 'POST', data : { title : title, completed : false }, success : function(data) { // 4.對data進行處理,並進行對應的dom渲染 }, error: function() { // 4.錯誤處理 } }); });
優化一些,咱們能夠分離事件處理程序和業務邏輯,在這裏,就不延伸舉例了。總之,傳統的開發模式,並無分層的概念,即沒有model、view、controller。好的方面是咱們能夠對單獨的業務邏輯進行抽取並單獨測試。並對這個部分代碼進行復用及封裝。壞的方面,當應用變得愈來愈複雜的時候,就會顯得代碼凌亂,維護性日益變差。
有同窗可能會說,還能夠結合面向對象、單命名空間的方式,讓代碼看起來更加優雅,更具可維護性。可是仍是沒有辦法有效去分離UI邏輯的頻繁變化(這裏僅僅針對富應用程序)。
5、總結
總之,既然學習了MVC這個設計模式,固然,咱們不必定非要去採用某一個框架(學習曲線、嵌入性、文件大小、兼容性、應用場景等等咱們都要進行考慮),咱們無需放大前端框架的做用,咱們須要領會的僅僅是其在前端應用的思想。就像最初jQuery模擬實現MVC的方式同樣,我再來總結幾個關鍵點——
1.構造模型Model
2.分離事件綁定,造成Controller
3.維護模型Model(and 模型集合Model Collection),經過Model的改變,通知對應的View從新渲染
4.分離View顯示邏輯
這樣,咱們藉助MVC的設計思想,可以現有代碼進行重構,固然也可以對將來的代碼進行必定展望。
固然,每個項目都有自身的特色,我的認爲,針對富應用(尤爲對增刪改的操做佔比較大的比例)的項目,MVC的設計模式具有必定的優點。