第四十五课:MVC,MVP,MVVM的区别
前端架構(gòu)從MVC到MVP,再到MVVM,它們都有不同的應(yīng)用場(chǎng)景。但MVVM已經(jīng)被證實(shí)為界面開發(fā)最好的方案了。
MVP?是從經(jīng)典的模式MVC演變而來,它們的基本思想有相通的地方:Controller/Presenter負(fù)責(zé)邏輯的處理,Model提供數(shù)據(jù),View負(fù) 責(zé)顯示。作為一種新的模式,MVP與MVC有著一個(gè)重大的區(qū)別:在MVP中View并不直接使用Model,它們之間的通信是通過Presenter來進(jìn)行的,所有的交互都發(fā)生在Presenter內(nèi)部,而在MVC中View會(huì)直接從Model中讀取數(shù)據(jù)而不是通過 Controller。
在MVC里,View是可以直接訪問Model的!從而,View里會(huì)包含Model信息,不可避免的還要包括一些業(yè)務(wù)邏輯。 MVC模型關(guān)注的是Model的不變,所以,在MVC模型里,Model不依賴于View,但是 View是依賴于Model的。不僅如此,因?yàn)橛幸恍I(yè)務(wù)邏輯在View里實(shí)現(xiàn)了,導(dǎo)致要更改View也是比較困難的,至少那些業(yè)務(wù)邏輯是無法重用的。
在MVP里,Presenter完全把Model和View進(jìn)行了分離,主要的程序邏輯在Presenter里實(shí)現(xiàn)。而且,Presenter與具體的 View是沒有直接關(guān)聯(lián)的,而是通過定義好的接口進(jìn)行交互,從而使得在變更View時(shí)候可以保持Presenter的不變,即重用!
在MVP里,應(yīng)用程序的邏輯主要在Presenter來實(shí)現(xiàn),其中的View是很薄的一層。在這個(gè)過程中,View是很簡(jiǎn)單的,能夠把信息顯示清楚就可以了。在后面,根據(jù)需要再隨便更改View, 而對(duì)Presenter沒有任何的影響了。 如果要實(shí)現(xiàn)的UI比較復(fù)雜,而且相關(guān)的顯示邏輯還跟Model有關(guān)系,就可以在View和Presenter之間放置一個(gè)Adapter。由這個(gè) Adapter來訪問Model和View,避免兩者之間的關(guān)聯(lián)。而同時(shí),因?yàn)锳dapter實(shí)現(xiàn)了View的接口,從而可以保證與Presenter之間接口的不變。這樣就可以保證View和Presenter之間接口的簡(jiǎn)潔,又不失去UI的靈活性。 在MVP模式里,View只應(yīng)該有簡(jiǎn)單的Set/Get的方法,用戶輸入和設(shè)置界面顯示的內(nèi)容,除此就不應(yīng)該有更多的內(nèi)容,絕不容許直接訪問 Model--這就是與MVC很大的不同之處。
MVVM在概念上是真正將頁面與數(shù)據(jù)邏輯分離的模式,它把數(shù)據(jù)綁定工作放到一個(gè)JS里去實(shí)現(xiàn),而這個(gè)JS文件的主要功能是完成數(shù)據(jù)的綁定,即把model綁定到UI的元素上。
大家都知道,我們前端使用MVC或MVP模式進(jìn)行開發(fā)時(shí),這個(gè)V與傳統(tǒng)意義上的V是不一樣的。在后端,這只是字符串的拼接,在前端,還涉及到DOM操作。即便你加入了模板,你也要將script標(biāo)簽中的模板內(nèi)容與后端返回的數(shù)據(jù)進(jìn)行結(jié)合,生成一個(gè)符合HTML結(jié)構(gòu)的字符串,最后,通過innerHTML轉(zhuǎn)換為頁面節(jié)點(diǎn),顯示出來。而這些操作,我們可以通過MVVM中的動(dòng)態(tài)模板搞定。它的原理大概是:動(dòng)態(tài)模板在掃描之后,會(huì)得到所有要處理的節(jié)點(diǎn)的引用,這也意味著,以后我們要做一小部分的更新,不用像靜態(tài)模板那樣大規(guī)模替換,而是細(xì)化到每一個(gè)元素節(jié)點(diǎn),特性節(jié)點(diǎn)或文本節(jié)點(diǎn)。這就是所謂的“最小化刷新”技術(shù)。一般的,只有ms-if等少量綁定才會(huì)影響到元素節(jié)點(diǎn)那一層面,更多的時(shí)候, 我們是在刷新特性節(jié)點(diǎn)的value值,文本節(jié)點(diǎn)的data值,這也意味著,我們的刷新不會(huì)引起reflow。加之,能得到元素節(jié)點(diǎn)本身,我們就可以輕松實(shí)現(xiàn)綁定事件,操作樣式,修改屬性等功能。這也是為什么大多數(shù)MVVM框架選擇動(dòng)態(tài)模板的緣故,jQuery原來可以做的,我們?nèi)客ㄟ^綁定屬性或定界符在HTML里搞定。 這也意味著,我們實(shí)現(xiàn)了完美的分層架構(gòu),JS里面是純粹的模型層(包括model與viewmodel),HTML里是視圖層。
此外,MVVM另一個(gè)重要特性,雙向綁定。它更方便你同時(shí)維護(hù)頁面上都依賴于某個(gè)字段的N個(gè)區(qū)域,而不用手動(dòng)更新它們。
有人做過測(cè)試:使用Angular(MVVM)代替Backbone(MVC)來開發(fā),代碼可以減少一半。
MVVM算一個(gè)很新的東西,后端誕生于2005年,前端誕生于2010年發(fā)布的knockout框架。目前主要有knockout.js,ember.js,angular.js,win.js,kendoui等。
了解完這些概念后,我們來看兩個(gè)用Backbone寫的例子,我們通過例子來詳細(xì)的了解下前端MVC是如何實(shí)現(xiàn)的:
1 <!DOCTYPE html>2 <html xmlns="http://www.w3.org/1999/xhtml">3 <head>4 <meta charset="utf-8" />5 <title></title>6 <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no" />7 <link rel="Stylesheet" type="text/css" href="res/style/main2.css" />8 <link rel="Stylesheet" type="text/css" href="res/style/tuan.css" />9 <style> .pro_list_rank { margin: 5px 0; padding-right: 22px; }10 .figcaption span { text-align: center; }11 .blog_item {}12 .blog_item img { width: 48px; height; 48px; margin: 4px; padding: 1px; float: left; border: 1px solid #CCC; }13 14 .blog_item .item_footer { color: #757575; font-size: 0.86em; }15 a { color: #005A94; }16 .tab_hotel { border-left: 1px solid #2B97E2; }17 .cont_wrap .content { background-color: White; padding: 5px 10px; }18 img { max-width: 98%; }</style>19 </head>20 <body>21 <div class="main-frame">22 <div class="main-viewport" id="main-viewport">23 </div>24 </div>25 <script type="text/template" id="index-template">26 <header>27 <b class="icon_home i_bef" id="js_home"></b>28 <h1>29 博客園</h1>30 31 <i id="js_return" class="returnico"></i>32 </header>33 <section class="cont_wrap">34 <div id="post"></div>35 <ul class="pro_list" id="lstbox">36 </ul>37 </section>38 <ul class="tab_search fix_bottom" id="sort">39 <li class="tabcrt" attr="updated">時(shí)間</li>40 <li class="tab_hotel" attr="diggs">推薦</li>41 <li class="tab_hotel" attr="views">閱讀</li>42 <li class="tab_hotel" attr="comments">評(píng)論</li>43 </ul>44 </script>45 <script type="text/template" id="item-template">46 <li class="arr_r orderItem" data-id="<%=id %>" data-index = "<%=index %>">47 <article class="blog_item">48 <h3>49 <a href="<%=link.href %>" target="_blank">50 <%=title.value || '無題' %></a>51 </h3>52 <div class="author pro_list_rank">53 <%if(author.avatar){ %>54 <a href="<%=author.uri %>" target="_blank">55 <img src="<%=author.avatar %>">56 </a>57 <%} %>58 <%=summary.value %>59 </div>60 <div class="item_footer">61 <a href="<%=author.uri %>" class="lightblue">Scut</a>62 <%=published %>63 <a href="<%=link.href %>" title="2013-08-21 15:21" class="gray">評(píng)論(<%=comments %>)</a>64 <a href="<%=link.href %>" class="gray">閱讀(<%=views %>)</a> <span class="price1">推薦(<%=diggs %>)</span></div>65 </article>66 </li>67 </script>68 <script type="text/template" id="detail-template">69 <section class="cont_wrap" >70 <article class="content">71 <h1>72 <a href="#"><%=title.value %></a></h1>73 <div style=" text-align: right; ">74 <time pubdate="pubdate" value="2013-04-15"><%=published %></time><br /><span>閱讀(<%=views %>)75 評(píng)論(<%=comments %>)</span>76 </div>77 <p><%=value %></p>78 </article>79 </section>80 </script>81 <script src="libs/jquery.js" type="text/javascript"></script>82 <script src="libs/underscore.js" type="text/javascript"></script>83 <script src="libs/backbone.js" type="text/javascript"></script>84 <script type="text/javascript" src="libs/backbone.localStorage.js"></script>85 <script type="text/javascript">86 //模型87 var PostModel = Backbone.Model.extend({88 89 });90 91 //模型集合92 var PostList = Backbone.Collection.extend({93 model: PostModel,94 parse: function (data) {95 96 return (data && data.feed && data.feed.entry) || {}97 },98 setComparator: function (type) {99 this.comparator = function (item) { 100 return Math.max(item.attributes[type]); 101 } 102 } 103 }); 104 //視圖,文章內(nèi)容的視圖 105 var Detail = Backbone.View.extend({ 106 el: $('#main-viewport'), 107 template: _.template($('#index-template').html()), 108 detail: _.template($('#detail-template').html()), 109 initialize: function (app) { 110 this.app = app; 111 this.$el.html(this.template()); 112 this.wrapper = $('#lstbox'); 113 this.render(); 114 }, 115 render: function () { 116 var scope = this; 117 var id = this.app.id; 118 119 var param = { url: 'http://wcf.open.cnblogs.com/blog/post/body/' + id } 120 121 var model = this.app.model; 122 123 $.get('Handler.ashx', param, function (data) { 124 (typeof data === 'string') && (data = $.parseJSON(data)); 125 if (data && data.string) { 126 //此處將content內(nèi)容寫入model 127 model.set('value', data.string.value); 128 scope.wrapper.html(scope.detail(model.toJSON())); 129 } 130 }); 131 132 }, 133 events: { 134 'click #js_return': function () { 135 this.app.forward('index') 136 } 137 } 138 }); 139 //視圖,文章列表的視圖 140 var Index = Backbone.View.extend({ 141 el: $('#main-viewport'), 142 template: _.template($('#index-template').html()), 143 itemTmpt: _.template($('#item-template').html()), 144 145 events: { 146 'click #sort': function (e) { 147 var el = $(e.target); 148 var type = el.attr('attr'); 149 this.list.setComparator(type); 150 this.list.sort(); 151 }, 152 'click .orderItem': function (e) { 153 var el = $(e.currentTarget); 154 var index = el.attr('data-index'); 155 var id = el.attr('data-id'); 156 var model = this.list.models[index]; 157 this.app.model = model; 158 this.app.id = id; 161 this.app.forward('detail'); 175 } 176 }, 177 initialize: function (app) { 178 this.app = app; 179 180 //先生成框架html 181 this.$el.html(this.template()); 182 this.post = this.$('#post'); 183 184 var scope = this; 185 var curpage = 1; 186 var pageSize = 10; 187 this.list = new PostList(); 188 this.list.url = 'Handler.ashx?url=http://wcf.open.cnblogs.com/blog/sitehome/paged/' + curpage + '/' + pageSize; 189 this.list.fetch({ 190 success: function () { 191 scope.render(); 192 } 193 }); 194 this.wrapper = $('#lstbox'); 195 196 this.listenTo(this.list, 'all', this.render); 197 198 }, 199 render: function () { 200 201 var models = this.list.models; 202 var html = ''; 203 for (var i = 0, len = models.length; i < len; i++) { 204 models[i].index = i; 205 html += this.itemTmpt(_.extend(models[i].toJSON(), { index: i })); 206 } 207 this.wrapper.html(html); 208 var s = ''; 209 } 210 }); 215 var App = Backbone.Router.extend({ 216 routes: { 217 "": "index", // #index 218 "index": "index", // #index 219 "detail": "detail" // #detail 220 }, 221 index: function () { 222 var index = new Index(this.interface); 223 224 }, 225 detail: function () { 226 var detail = new Detail(this.interface); 227 228 }, 229 initialize: function () { 231 }, 232 interface: { 233 forward: function (url) { 234 window.location.href = ('#' + url).replace(/^#+/, '#'); 235 } 236 237 } 240 }); 242 var app = new App(); 243 Backbone.history.start(); 245 var s = ''; 247 </script> 248 </body> 249 </html>我們來分析這段代碼時(shí),只需要看js代碼。代碼的一開始,我們先定義了一個(gè)模型PostModel,這個(gè)模型相當(dāng)于后臺(tái)返回的一條數(shù)據(jù)。然后定義了一個(gè)PostList集合,它里面的每一項(xiàng)就是模型PostModel。集合PostList有兩個(gè)方法,一個(gè)是parse方法,它用于解析后臺(tái)返回的數(shù)據(jù),會(huì)自動(dòng)調(diào)用,因此你可以重寫此方法,改變后臺(tái)數(shù)據(jù)的表現(xiàn)形式。第二個(gè)方法setComparator用來設(shè)置模型集合排序時(shí),使用的比較方法(比如:模型集合PostList.sort(),會(huì)對(duì)里面的模型進(jìn)行排序,這時(shí)排序調(diào)用的比較方法就是comparator)。
接下來,定義了一個(gè)視圖Detail,此視圖是用來顯示文章內(nèi)容的。由于它只顯示一篇文章,所以它只操作一個(gè)模型,這里就是操作PostModel。
然后,定義了一個(gè)視圖Index,此視圖是用來顯示文章列表的,由于它顯示很多文章的標(biāo)題,因此它操作的就是模型集合PostList。
最后定義了一個(gè)路由App,我們也可以叫它Controller。它主要通過Hash值的變化,來改變視圖的。
我們總共定義了兩個(gè)視圖,一個(gè)模型,一個(gè)集合,一個(gè)路由。那我們?nèi)绾问褂盟麄兡?#xff0c;首先初始化一個(gè)路由對(duì)象,然后啟動(dòng)路由功能。路由的使用,我們不僅需要初始化一個(gè)對(duì)象,而且必須調(diào)用Backbone.history.start()。
當(dāng)用戶輸入url訪問這個(gè)頁面的時(shí)候,比如:www.chaojidan.com,這時(shí)沒有hash值,因此會(huì)調(diào)用路由中的index方法,這時(shí),就會(huì)初始化Index視圖,并把路由中的interface對(duì)象傳進(jìn)這個(gè)視圖。實(shí)例化Index視圖時(shí),就會(huì)調(diào)用Index的initialize方法,在此方法中,又會(huì)實(shí)例化一個(gè)集合PostList對(duì)象list,然后通過這個(gè)集合對(duì)象向后臺(tái)請(qǐng)求數(shù)據(jù),數(shù)據(jù)返回后,就會(huì)存儲(chǔ)在集合對(duì)象list中,這時(shí)就會(huì)調(diào)用視圖Index的render方法,此方法,就會(huì)把集合list中的數(shù)據(jù)全部顯示出來。同時(shí),視圖中的events對(duì)象,就會(huì)自動(dòng)綁定一些事件。
當(dāng)我們點(diǎn)擊.orderItem這個(gè)元素(此元素就是文章列表)時(shí),就會(huì)執(zhí)行回調(diào)方法,此回調(diào)方法,就會(huì)讓頁面上顯示此文章的內(nèi)容,也就是視圖的變化。在這個(gè)回調(diào)方法中,會(huì)調(diào)用路由的forward方法,此方法就會(huì)改變頁面的url,這時(shí)url會(huì)變成www.chaojidan.com#detail。由于hash值變化了,這時(shí)就會(huì)調(diào)用路由中的回調(diào)方法detail,而此方法就會(huì)實(shí)例化一個(gè)detail視圖對(duì)象。
在detail視圖中,就會(huì)去獲取你點(diǎn)擊的文章的內(nèi)容,然后顯示在頁面上。
大家看懂這個(gè)代碼后,再來考慮下,它的MVC模式是如何體現(xiàn)的?
首先model模型PostModel,它對(duì)應(yīng)后臺(tái)的一條數(shù)據(jù),collection集合PostList,它對(duì)應(yīng)后臺(tái)的多條數(shù)據(jù)。與后臺(tái)交互的是collection,集合的功能就是從后臺(tái)請(qǐng)求數(shù)據(jù),然后把數(shù)據(jù)進(jìn)行解析,每一條數(shù)據(jù)就是一個(gè)model。
然后視圖Index是用來顯示集合的的數(shù)據(jù),也就是顯示多個(gè)model。視圖detail用來顯示單條數(shù)據(jù),這里的數(shù)據(jù)是文章內(nèi)容,而collection集合中的數(shù)據(jù)是文章標(biāo)題,也就是說在Index視圖中,模型model只是一個(gè)文章標(biāo)題,而在detail視圖中,模型model是文章的內(nèi)容。這里的視圖是用模板的形式把數(shù)據(jù)套進(jìn)去,然后添加到頁面上的,每次模型的數(shù)據(jù)變化,都會(huì)進(jìn)行模板重新組裝,即便是改變了一個(gè)數(shù)據(jù),就要把整個(gè)模板進(jìn)行組裝,是不是有點(diǎn)浪費(fèi)呢?
視圖之間的切換,是通過router路由來實(shí)現(xiàn)的,因?yàn)橐晥D中綁定了一些方法,比如在文章列表中綁定了click事件,當(dāng)你點(diǎn)擊文章列表中的一項(xiàng)時(shí)(也就是想看此文章的內(nèi)容時(shí)),就會(huì)改變hash值(改變hash的值,不會(huì)請(qǐng)求服務(wù)器),這時(shí)因?yàn)閱?dòng)了路由功能,所以就會(huì)調(diào)用此hash值對(duì)應(yīng)的方法,然后初始化detail視圖,此視圖,就會(huì)去后臺(tái)取此文章的內(nèi)容,然后顯示在此頁面上。
如果公司中的項(xiàng)目用Backbone來實(shí)現(xiàn),然后加上sea.js來進(jìn)行模塊化開發(fā),那么,我們可以在init.js中,引入路由這個(gè)模塊,然后初始一個(gè)路由對(duì)象,并調(diào)用Backbone.historty.start()來啟動(dòng)此路由。而這個(gè)路由模塊中,定義了一個(gè)跟菜單選項(xiàng)相對(duì)應(yīng)的路由表,比如:第一個(gè)菜單,就是默認(rèn)顯示的,那么,它的hash值對(duì)應(yīng)"",當(dāng)用戶訪問www.chaojidan.com時(shí),就會(huì)調(diào)用此hash對(duì)應(yīng)的回調(diào)方法,然后加載此菜單需要的js文件,也就是模塊(這里面其實(shí)就是定義了View和Model),這里通過sea.js中的require.async方法加載,加載成功后,就會(huì)實(shí)例化此View和Model,在View中就會(huì)進(jìn)行初始化操作,然后就會(huì)通過model向服務(wù)器請(qǐng)求數(shù)據(jù),最后通過View顯示在頁面中。
點(diǎn)擊一個(gè)菜單,就會(huì)改變hash的值,就會(huì)執(zhí)行相對(duì)應(yīng)的回調(diào)方法, 然后就會(huì)加載相對(duì)應(yīng)的js文件(模塊),最后就會(huì)請(qǐng)求服務(wù)器返回?cái)?shù)據(jù),把數(shù)據(jù)顯示在頁面上。
這里的js文件(模塊),只有你點(diǎn)擊相對(duì)應(yīng)的菜單欄時(shí),才會(huì)去后臺(tái)下載并解析,是否能夠很好的處理同時(shí)加載太多js文件導(dǎo)致的頁面假死情況。
這里面需要注意的是在js文件(模塊)中,我們的initialize方法,一開始就需要調(diào)用thie.el.off()方法,此方法,就是取消此視圖中的之前所有的事件綁定,以防你重復(fù)綁定。
這一課,在概念上,知道了MVC和MVVM的區(qū)別,然后從實(shí)際上知道了MVC的開發(fā)模式。
下一課,我們將從實(shí)際上來講解MVC和MVVM的區(qū)別。
?
?
?
加油!
轉(zhuǎn)載于:https://www.cnblogs.com/chaojidan/p/4223441.html
總結(jié)
以上是生活随笔為你收集整理的第四十五课:MVC,MVP,MVVM的区别的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 海量数据处理之蓄水池抽样算法
- 下一篇: mysql query cache优化