11年剛開(kāi)始用前端MVC框架時(shí)寫(xiě)過(guò)一篇文章,當(dāng)時(shí)Knockout和Backbone都在用,但之后的項(xiàng)目全是在用Backbone,主要因?yàn)樗?jiǎn)單、靈活,無(wú)論是富JS應(yīng)用還是企業(yè)網(wǎng)站都用得上。相比React針對(duì)View和單向數(shù)據(jù)流的設(shè)計(jì),Backbone更能體現(xiàn)MVC的思路,所以針對(duì)它寫(xiě)一篇入門(mén)范例,說(shuō)明如下:
創(chuàng)新互聯(lián)專注于企業(yè)成都全網(wǎng)營(yíng)銷、網(wǎng)站重做改版、利州網(wǎng)站定制設(shè)計(jì)、自適應(yīng)品牌網(wǎng)站建設(shè)、H5響應(yīng)式網(wǎng)站、商城開(kāi)發(fā)、集團(tuán)公司官網(wǎng)建設(shè)、外貿(mào)網(wǎng)站制作、高端網(wǎng)站制作、響應(yīng)式網(wǎng)頁(yè)設(shè)計(jì)等建站業(yè)務(wù),價(jià)格優(yōu)惠性價(jià)比高,為利州等各大城市提供網(wǎng)站開(kāi)發(fā)制作服務(wù)。
1. 結(jié)構(gòu)上分4節(jié),介紹Model/View/Collection,實(shí)現(xiàn)從遠(yuǎn)程獲取數(shù)據(jù)顯示到表格且修改刪除;
2. 名為“范例”,所以代碼為主,每節(jié)的第1段代碼都是完整代碼,復(fù)制粘貼就能用,每段代碼都是基于前一段代碼來(lái)寫(xiě)的,因此每段代碼的新內(nèi)容不會(huì)超過(guò)20行(大括號(hào)計(jì)算在內(nèi));
3. 每行代碼沒(méi)有注釋,但重要內(nèi)容之后有寫(xiě)具體的說(shuō)明;
4. 開(kāi)發(fā)環(huán)境是Chrome,使用github的API,這樣用Chrome即使在本地路徑(形如file://的路徑)也能獲取數(shù)據(jù)。
0. Introduction
幾乎所有的框架都是在做兩件事:一是幫你把代碼寫(xiě)在正確的地方;二是幫你做一些臟活累活。Backbone實(shí)現(xiàn)一種清晰的MVC代碼結(jié)構(gòu),解決了數(shù)據(jù)模型和視圖映射的問(wèn)題。雖然所有JS相關(guān)的項(xiàng)目都可以用,但Backbone最適合的還是這樣一種場(chǎng)景:需要用JS生成大量的頁(yè)面內(nèi)容(HTML為主),用戶跟頁(yè)面元素有很多的交互行為。
Backbone對(duì)象有5個(gè)重要的函數(shù),Model/Collection/View/Router/History。Router和History是針對(duì)Web應(yīng)用程序的優(yōu)化,建議先熟悉pushState的相關(guān)知識(shí)。入門(mén)階段可以只了解Model/Collection/View。將Model視為核心,Collection是Model的集合,View是為了實(shí)現(xiàn)Model的改動(dòng)在前端的反映。
1. Model
Model是所有JS應(yīng)用的核心,很多Backbone教程喜歡從View開(kāi)始講,其實(shí)View的內(nèi)容不多,而且理解了View意義不大,理解Model更重要。以下代碼實(shí)現(xiàn)從github的API獲取一條gist信息,顯示到頁(yè)面上:
<!DOCTYPE html> <html> <head> <script type="text/javascript" src="https://code.jquery.com/jquery-1.11.1.js"></script> <script type="text/javascript" src="http://underscorejs.org/underscore-min.js"></script> <script type="text/javascript" src="http://backbonejs.org/backbone-min.js"></script> <link rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="stylesheet"> </head> <body> <table id="js-id-gists" class="table"> <thead><th>description</th><th>URL</th><th>created_at</th></thead> <tbody></tbody> </table> <script type="text/javascript"> var Gist = Backbone.Model.extend({ url: 'https://api.github.com/gists/public', parse: function (response) { return (response[0]); } }), gist = new Gist(); gist.on('change', function (model) { var tbody = document.getElementById('js-id-gists').children[1], tr = document.getElementById(model.get('id')); if (!tr) { tr = document.createElement('tr'); tr.setAttribute('id', model.get('id')); } tr.innerHTML = '<td>' + model.get('description') + '</td><td>' + model.get('url') + '</td><td>' + model.get('created_at') + '</td>'; tbody.appendChild(tr); }); gist.fetch(); </script> </body> </html>
LINE4~8: 加載要用到的JS庫(kù)。ajax請(qǐng)求和部分View的功能需要jQuery支持(或者重寫(xiě)ajax/View的功能);Backbone的代碼是基于Underscore寫(xiě)的(或者用Lo-Dash代替);加載bootstrap.css只是因?yàn)槟J(rèn)樣式太難看…
LINE16~22: 創(chuàng)建一個(gè)Model并實(shí)例化。url是數(shù)據(jù)源(API接口)的地址,parse用來(lái)處理返回的數(shù)據(jù)。實(shí)際返回的是一個(gè)Array,這里取第一個(gè)Object。
LINE24~33: 綁定change事件。還沒(méi)有使用View,所以要自己處理HTML。這10行代碼主要是get的用法(model.get),其他的功能之后會(huì)用View來(lái)實(shí)現(xiàn)。
LINE34: 執(zhí)行fetch。從遠(yuǎn)程獲取數(shù)據(jù),獲到數(shù)據(jù)后會(huì)觸發(fā)change事件??梢灾貙?xiě)sync方法
打開(kāi)Chrome的Console,輸入gist,可以看到Model獲得的屬性:
Model提供數(shù)據(jù)和與數(shù)據(jù)相關(guān)的邏輯。上圖輸出的屬性是數(shù)據(jù),代碼中的fetch/parse/get/set都是對(duì)數(shù)據(jù)進(jìn)行操作,其他的還有escape/unset/clear/destory,從函數(shù)名字就大致可以明白它的用途。還有很常用的validate函數(shù),在set/save操作時(shí)用來(lái)做數(shù)據(jù)驗(yàn)證,驗(yàn)證失敗會(huì)觸發(fā)invalid事件:
/* 替換之前代碼的JS部分(LINE16~34) */ var Gist = Backbone.Model.extend({ url: 'https://api.github.com/gists/public', parse: function (response) { return (response[0]); }, defaults: { website: 'dmyz' }, validate: function (attrs) { if (attrs.website == 'dmyz') { return 'Website Error'; } } }), gist = new Gist(); gist.on('invalid', function (model, error) { alert(error); }); gist.on('change', function (model) { var tbody = document.getElementById('js-id-gists').children[1], tr = document.getElementById(model.get('id')); if (!tr) { tr = document.createElement('tr'); tr.setAttribute('id', model.get('id')); } tr.innerHTML = '<td>'+ model.get('description') +'</td><td>'+ model.get('url') +'</td><td>'+ model.get('created_at') +'</td>'; tbody.appendChild(tr); }); gist.save();
跟之前的代碼比較,有4處改動(dòng):
LINE7~9: 增加了defaults。如果屬性中沒(méi)有website(注意不是website值為空),會(huì)設(shè)置website值為dmyz。
LINE10~14: 增加validate函數(shù)。當(dāng)website值為dmyz時(shí),觸發(fā)invalid事件。
LINE18~20: 綁定invalid事件,alert返回的錯(cuò)誤。
LINE31: 不做fetch,直接save操作。
因?yàn)闆](méi)有fetch,所以頁(yè)面上不會(huì)顯示數(shù)據(jù)。執(zhí)行save操作,會(huì)調(diào)用validate函數(shù),驗(yàn)證失敗會(huì)觸發(fā)invalid事件,alert出錯(cuò)誤提示。同時(shí)save操作也會(huì)向Model的URL發(fā)起一個(gè)PUT請(qǐng)求,github這個(gè)API沒(méi)有處理PUT,所以會(huì)返回404錯(cuò)誤。
在Console中輸入gist.set(‘description', ‘demo'),可以看到頁(yè)面元素也會(huì)有相應(yīng)的變化。執(zhí)行g(shù)ist.set(‘description', gist.previous(‘description'))恢復(fù)之前的值。這就是Model和View的映射,現(xiàn)在還是自己實(shí)現(xiàn)的,下一節(jié)會(huì)用Backbone的View來(lái)實(shí)現(xiàn)。
2. View
用Backbone的View來(lái)改寫(xiě)之前代碼LINE24~33部分:
<!DOCTYPE html> <html> <head> <script type="text/javascript" src="https://code.jquery.com/jquery-1.11.1.js"></script> <script type="text/javascript" src="http://underscorejs.org/underscore-min.js"></script> <script type="text/javascript" src="http://backbonejs.org/backbone-min.js"></script> <link rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="stylesheet"> </head> <body> <table id="js-id-gists" class="table"> <thead><th>description</th><th>URL</th><th>created_at</th><th></th></thead> <tbody></tbody> </table> <script type="text/javascript"> var Gist = Backbone.Model.extend({ url: 'https://api.github.com/gists/public', parse: function (response) { return response[0]; } }), gist = new Gist(); var GistRow = Backbone.View.extend({ el: 'tbody', MODEL: gist, events: { 'click a': 'replaceURL' }, replaceURL: function () { this.MODEL.set('url', 'http://dmyz.org'); }, initialize: function () { this.listenTo(this.MODEL, 'change', this.render); }, render: function () { var model = this.MODEL, tr = document.createElement('tr'); tr.innerHTML = '<td>' + model.get('description') + '</td><td>' + model.get('url') + '</td><td>' + model.get('created_at') + '</td><td><a href="javascript:void(0)" rel="external nofollow" rel="external nofollow" rel="external nofollow" >®</a></td>'; this.el.innerHTML = tr.outerHTML; return this; } }); var tr = new GistRow(); gist.fetch(); </script> </body> </html>
LINE25: 所有的View都是基于DOM的,指定el會(huì)選擇頁(yè)面的元素,指定tagName會(huì)創(chuàng)建相應(yīng)的DOM,如果都沒(méi)有指定會(huì)是一個(gè)空的div。
LINE27~32: 綁定click事件到a標(biāo)簽,replaceURL函數(shù)會(huì)修改(set)url屬性的值。
LINE33~35: View的初始化函數(shù)(initialize),監(jiān)聽(tīng)change事件,當(dāng)Model數(shù)據(jù)更新時(shí)觸發(fā)render函數(shù)。
LINE36~42: render函數(shù)。主要是LINE41~42這兩行,把生成的HTML代碼寫(xiě)到this.el,返回this。
LINE44: 實(shí)例化GistRow,初始化函數(shù)(initialize)會(huì)被執(zhí)行。
點(diǎn)擊行末的a標(biāo)簽,頁(yè)面顯示的這條記錄的URL會(huì)被修改成http://dmyz.org。
這個(gè)View名為GistRow,選擇的卻是tbody標(biāo)簽,這顯然是不合理的。接下來(lái)更改JS代碼,顯示API返回的30條數(shù)據(jù):
/* 替換之前代碼的JS部分(LINE16~45) */ var Gist = Backbone.Model.extend(), Gists = Backbone.Model.extend({ url: 'https://api.github.com/gists/public', parse: function (response) { return response; } }), gists = new Gists(); var GistRow = Backbone.View.extend({ tagName: 'tr', render: function (object) { var model = new Gist(object); this.el.innerHTML = '<td>' + model.get('description') + '</td><td>'+ model.get('url') + '</td><td>' + model.get('created_at') + '</td><td></td>' return this; } }); var GistsView = Backbone.View.extend({ el: 'tbody', model: gists, initialize: function () { this.listenTo(this.model, 'change', this.render); }, render: function () { var html = ''; _.forEach(this.model.attributes, function (object) { var tr = new GistRow(); html += tr.render(object).el.outerHTML; }); this.el.innerHTML = html; return this; } }); var gistsView = new GistsView(); gists.fetch();
LINE2~9: 創(chuàng)建了兩個(gè)Model(Gist和Gists),parse現(xiàn)在返回完整Array而不只是第一條。
LINE11~18: 創(chuàng)建一個(gè)tr。render方法會(huì)傳一個(gè)Object來(lái)實(shí)例化一個(gè)Gist的Model,再?gòu)倪@個(gè)Model里get需要的值。
LINE26~34: 遍歷Model中的所有屬性?,F(xiàn)在使用的是Model而不是Collection,所以遍歷出的是Object。forEach是Underscore的函數(shù)。
Backbone的View更多的是組織代碼的作用,它實(shí)際干的活很少。View的model屬性在本節(jié)第一段代碼用的是大寫(xiě),表明只是一個(gè)名字,并不是說(shuō)給View傳一個(gè)Model它會(huì)替你完成什么,控制邏輯還是要自己寫(xiě)。還有View中經(jīng)常會(huì)用到的template函數(shù),也是要自己定義的,具體結(jié)合哪種模板引擎來(lái)用就看自己的需求了。
這段代碼中的Gists比較難操作其中的每一個(gè)值,它其實(shí)應(yīng)該是Gist的集合,這就是Backbone的Collection做的事了。
3. Collection
Collection是Model的集合,在這個(gè)Collection中的Model如果觸發(fā)了某個(gè)事件,可以在Collection中接收到并做處理。第2節(jié)的代碼用Collection實(shí)現(xiàn):
<!DOCTYPE html> <html> <head> <script type="text/javascript" src="https://code.jquery.com/jquery-1.11.1.js"></script> <script type="text/javascript" src="http://underscorejs.org/underscore-min.js"></script> <script type="text/javascript" src="http://backbonejs.org/backbone-min.js"></script> <link rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="stylesheet"> </head> <body> <table id="js-id-gists" class="table"> <thead><th>description</th><th>URL</th><th>created_at</th><th></th></thead> <tbody></tbody> </table> <script type="text/javascript"> var Gist = Backbone.Model.extend(), Gists = Backbone.Collection.extend({ model: Gist, url: 'https://api.github.com/gists/public', parse: function (response) { return response; } }), gists = new Gists(); var GistRow = Backbone.View.extend({ tagName: 'tr', render: function (model) { this.el.innerHTML = '<td>' + model.get('description') + '</td><td>'+ model.get('url') + '</td><td>' + model.get('created_at') + '</td><td></td>' return this; } }); var GistsView = Backbone.View.extend({ el: 'tbody', collection: gists, initialize: function () { this.listenTo(this.collection, 'reset', this.render); }, render: function () { var html = ''; _.forEach(this.collection.models, function (model) { var tr = new GistRow(); html += tr.render(model).el.outerHTML; }); this.el.innerHTML = html; return this; } }); var gistsView = new GistsView(); gists.fetch({reset: true}); </script> </body> </html>
LINE17~23: 基本跟第2節(jié)的第2段代碼一樣。把Model改成Collection,指定Collection的Model,這樣Collectio獲得返回值會(huì)自動(dòng)封裝成Model的Array。
LINE38: Collection和Model不同,獲取到數(shù)據(jù)也不會(huì)觸發(fā)事件,所以綁定一個(gè)reset事件,在之后的fetch操作中傳遞{reset: true}。
LINE42~45: 從Collection從遍歷Model,傳給GistRow這個(gè)View,生成HTML。
Collection是Backbone里功能最多的函數(shù)(雖然其中很多是Underscore的),而且只要理解了Model和View的關(guān)系,使用Collection不會(huì)有任何障礙。給Collection綁定各種事件來(lái)實(shí)現(xiàn)豐富的交互功能了,以下這段JS代碼會(huì)加入刪除/編輯的操作,可以在JSBIN上查看源代碼和執(zhí)行結(jié)果。只是增加了事件,沒(méi)有什么新內(nèi)容,所以就不做說(shuō)明了,附上JSBIN的演示地址:http://jsbin.com/jevisopo/1
/* 替換之前代碼的JS部分(LINE16~51) */ var Gist = Backbone.Model.extend(), Gists = Backbone.Collection.extend({ model: Gist, url: 'https://api.github.com/gists/public', parse: function (response) { return response; } }), gists = new Gists(); var GistRow = Backbone.View.extend({ tagName: 'tr', render: function (model) { this.el.id = model.cid; this.el.innerHTML = '<td>' + model.get('description') + '</td><td>'+ model.get('url') + '</td><td>' + model.get('created_at') + '</td><td><a href="javascript:void(0)" rel="external nofollow" rel="external nofollow" rel="external nofollow" class="js-remove">X</a> <a href="javascript:void(0)" rel="external nofollow" rel="external nofollow" rel="external nofollow" class="js-edit">E</a> </td>' return this; } }); var GistsView = Backbone.View.extend({ el: 'tbody', collection: gists, events: { 'click a.js-remove': function (e) { var cid = e.currentTarget.parentElement.parentElement.id; gists.get(cid).destroy(); gists.remove(cid); }, 'click a.js-edit': 'editRow', 'blur td[contenteditable]': 'saveRow' }, editRow: function (e) { var tr = e.currentTarget.parentElement.parentElement, i = 0; while (i < 3) { tr.children[i].setAttribute('contenteditable', true); i++; } }, saveRow: function (e) { var tr = e.currentTarget.parentElement, model = gists.get(tr.id); model.set({ 'description' : tr.children[0].innerText, 'url': tr.children[1].innerText, 'created_at': tr.children[2].innerText }); model.save(); }, initialize: function () { var self = this; _.forEach(['reset', 'remove', 'range'], function (e) { self.listenTo(self.collection, e, self.render); }); }, render: function () { var html = ''; _.forEach(this.collection.models, function (model) { var tr = new GistRow(); html += tr.render(model).el.outerHTML; }); this.el.innerHTML = html; return this; } }); var gistsView = new GistsView(); gists.fetch({reset: true});
Afterword
雖然是入門(mén)范例,但因?yàn)槠邢?,有些基本語(yǔ)言特征和Backbone的功能不可能面面俱到,如果還看不懂肯定是我漏掉了需要解釋的點(diǎn),請(qǐng)(在Google之后)評(píng)論或是郵件告知。
Backbone不是jQuery插件,引入以后整個(gè)DOM立即實(shí)現(xiàn)增刪改查了,也做不到KnockoutJS/AnglarJS那樣,在DOM上做數(shù)據(jù)綁定就自動(dòng)完成邏輯。它是將一些前端工作處理得更好更規(guī)范,如果學(xué)習(xí)前端MVC的目的是想輕松完成工作,Backbone可能不是最佳選擇。如果有一個(gè)項(xiàng)目,100多行HTML和1000多行JS,JS主要都在操作頁(yè)面DOM(如果討厭+號(hào)連接HTML還可以搭配React/JSX來(lái)寫(xiě)),那就可以考慮用Backbone來(lái)重寫(xiě)了,它比其他龐大的MVC框架要容易掌握得多,作為入門(mén)學(xué)習(xí)也是非常不錯(cuò)的。
網(wǎng)站題目:BACKBONE.JS簡(jiǎn)單入門(mén)范例
分享路徑:http://m.rwnh.cn/article44/jcjshe.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供品牌網(wǎng)站設(shè)計(jì)、營(yíng)銷型網(wǎng)站建設(shè)、網(wǎng)站排名、商城網(wǎng)站、服務(wù)器托管、小程序開(kāi)發(fā)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)