深入解析JavaScript框架Backbone.js中的事件機(jī)制
事件模型及其原理
Backbone.Events就是事件實(shí)現(xiàn)的核心,它可以讓對(duì)象擁有事件能力
var Events = Backbone.Events = { .. }
對(duì)象通過(guò)listenTo偵聽(tīng)其他對(duì)象,通過(guò)trigger觸發(fā)事件??梢悦撾xBackbone的MVC,在自定義的對(duì)象上使用事件
var model = _.extend({},Backbone.Events); var view = _.extend({},Backbone.Events); view.listenTo(model,'custom_event',function(){ alert('catch the event') }); model.trigger('custom_event');
執(zhí)行結(jié)果:
Backbone的Model和View等核心類(lèi),都是繼承自Backbone.Events的。例如Backbone.Model:
var Events = Backbone.Events = { .. } var Model = Backbone.Model = function(attributes, options) { ... }; _.extend(Model.prototype, Events, { ... })
從原理上講,事件是這么工作的:
被偵聽(tīng)的對(duì)象維護(hù)一個(gè)事件數(shù)組_event,其他對(duì)象在調(diào)用listenTo時(shí),會(huì)將事件名與回調(diào)維護(hù)到隊(duì)列中:
一個(gè)事件名可以對(duì)應(yīng)多個(gè)回調(diào),對(duì)于被偵聽(tīng)者而言,只知道回調(diào)的存在,并不知道具體是哪個(gè)對(duì)象在偵聽(tīng)它。當(dāng)被偵聽(tīng)者調(diào)用trigger(name)時(shí),會(huì)遍歷_event,選擇同名的事件,并將其下面所有的回調(diào)都執(zhí)行一遍。
需要額外注意的是,Backbone的listenTo實(shí)現(xiàn),除了使被偵聽(tīng)者維護(hù)對(duì)偵聽(tīng)者的引用外,還使偵聽(tīng)者也維護(hù)了被偵聽(tīng)者。這是為了在恰當(dāng)?shù)臅r(shí)候,偵聽(tīng)者可以單方面中斷偵聽(tīng)。因此,雖然是循環(huán)引用,但是使用Backbone的合適的方法可以很好的維護(hù),不會(huì)有問(wèn)題,在后面的內(nèi)存泄露部分將看到。
另外,有時(shí)只希望事件在綁定后,當(dāng)回調(diào)發(fā)生后,就接觸綁定。這在一些對(duì)公共模塊的引用時(shí)很有用。listenToOnce可以做到這一點(diǎn)
與服務(wù)器同步數(shù)據(jù)
backbone默認(rèn)實(shí)現(xiàn)了一套與RESTful風(fēng)格的服務(wù)端同步模型的機(jī)制,這套機(jī)制不僅可以減輕開(kāi)發(fā)人員的工作量,而且可以使模型變得更為健壯(在各種異常下仍能保持?jǐn)?shù)據(jù)一致性)。不過(guò),要真正發(fā)揮這個(gè)功效,一個(gè)與之匹配的服務(wù)端實(shí)現(xiàn)是很重要的。為了說(shuō)明問(wèn)題,假設(shè)服務(wù)端有如下REST風(fēng)格的接口:
- GET /resources 獲取資源列表
- POST /resources 創(chuàng)建一個(gè)資源,返回資源的全部或部分字段
- GET /resources/{id} 獲取某個(gè)id的資源詳情,返回資源的全部或部分字段
- DELETE /resources/{id} 刪除某個(gè)資源
- PUT /resources/{id} 更新某個(gè)資源的全部字段,返回資源的全部或部分字段
- PATCH /resources/{id} 更新某個(gè)資源的部分字段,返回資源的全部或部分字段
backbone會(huì)使用到上面這些HTTP方法的地方主要有以下幾個(gè):
- Model.save() 邏輯上,根據(jù)當(dāng)前這個(gè)model的是否具有id來(lái)判斷應(yīng)該使用POST還是PUT,如果model沒(méi)有id,表示是新的模型,將使用POST,將模型的字段全部提交到/resources;如果model具有id,表示是已經(jīng)存在的模型,將使用PUT,將模型的全部字段提交到/resources/{id}。當(dāng)傳入options包含patch:true的時(shí)候,save會(huì)產(chǎn)生PATCH。
- Model.destroy() 會(huì)產(chǎn)生DELETE,目標(biāo)url為/resources/{id},如果當(dāng)前model不包含id時(shí),不會(huì)與服務(wù)端同步,因?yàn)榇藭r(shí)backbone認(rèn)為model在服務(wù)端尚不存在,不需要?jiǎng)h除
- Model.fetch() 會(huì)產(chǎn)生GET,目標(biāo)url為/resources/{id},并將獲得的屬性更新model。
- Collection.fetch() 會(huì)產(chǎn)生GET,目標(biāo)url為/resources,并對(duì)返回的數(shù)組中的每個(gè)對(duì)象,自動(dòng)實(shí)例化model
- Collection.create() 實(shí)際將調(diào)用Model.save
options參數(shù)存在于上面任何一個(gè)方法的參數(shù)列表中,通過(guò)options可以修改backbone和ajax請(qǐng)求的一些行為,可以使用的options包括:
- wait: 可以指定是否等待服務(wù)端的返回結(jié)果再更新model。默認(rèn)情況下不等待
- url: 可以覆蓋掉backbone默認(rèn)使用的url格式
- attrs: 可以指定保存到服務(wù)端的字段有哪些,配合options.patch可以產(chǎn)生PATCH對(duì)模型進(jìn)行部分更新
- patch: 指定使用部分更新的REST接口
- data: 會(huì)被直接傳遞給jquery的ajax中的data,能夠覆蓋backbone所有的對(duì)上傳的數(shù)據(jù)控制的行為
- 其他: options中的任何參數(shù)都將直接傳遞給jquery的ajax,作為其options
backbone通過(guò)Model的urlRoot屬性或者是Collection的url屬性得知具體的服務(wù)端接口地址,以便發(fā)起ajax。在Model的url默認(rèn)實(shí)現(xiàn)中,Model除了會(huì)考察urlRoot,第二選擇會(huì)是Model所在Collection的url,所有有時(shí)只需要在Collection里面書(shū)寫(xiě)url就可以了。
Backbone會(huì)根據(jù)與服務(wù)端要進(jìn)行什么類(lèi)型的操作,決定是否要添加id在url后面,以下代碼是Model的默認(rèn)url實(shí)現(xiàn):
url: function () { var base = _.result(this, 'urlRoot') || _.result(this.collection, 'url') || urlError(); if (this.isNew()) return base; return base.replace(/([^\/])$/, '$1/') + encodeURIComponent(this.id); },
其中的正則式/([^\/])$/是個(gè)很巧妙的處理,它解決了url最后是否包含'/'的不確定性。
這個(gè)正則匹配的是行末的非/字符,這樣,像/resources這樣的目標(biāo)會(huì)匹配s,然后replace中使用分組編號(hào)$1捕獲了s,將s替換為s/,這樣就自動(dòng)加上了缺失的/;而當(dāng)/resources/這樣目標(biāo)卻無(wú)法匹配到結(jié)果,也就不需要替換了。
Model和Collection的關(guān)系
在backbone中,即便一類(lèi)的模型實(shí)例的確是在一個(gè)集合里面,也并沒(méi)有強(qiáng)制要求使用集合類(lèi)。但是使用集合有一些額外的好處,這些好處包括:
url繼承
Model屬于Collection后,可以繼承Collection的url屬性。Collection沿用了underscore90%的集合和數(shù)組操作,使得集合操作極其方便:
// Underscore methods that we want to implement on the Collection. // 90% of the core usefulness of Backbone Collections is actually implemented // right here: var methods = ['forEach', 'each', 'map', 'collect', 'reduce', 'foldl', 'inject', 'reduceRight', 'foldr', 'find', 'detect', 'filter', 'select', 'reject', 'every', 'all', 'some', 'any', 'include', 'contains', 'invoke', 'max', 'min', 'toArray', 'size', 'first', 'head', 'take', 'initial', 'rest', 'tail', 'drop', 'last', 'without', 'difference', 'indexOf', 'shuffle', 'lastIndexOf', 'isEmpty', 'chain', 'sample']; Backbone巧妙的使用下面的代碼將這些方法附加到Collection中: // Mix in each Underscore method as a proxy to `Collection#models`. _.each(methods, function (method) { Collection.prototype[method] = function () { var args = slice.call(arguments); //將參數(shù)數(shù)組轉(zhuǎn)化成真正的數(shù)組 args.unshift(this.models); //將Collection真正用來(lái)維護(hù)集合的數(shù)組,作為第一個(gè)個(gè)參數(shù) return _[method].apply(_, args); //使用apply調(diào)用underscore的方法 }; });
自動(dòng)偵聽(tīng)和轉(zhuǎn)發(fā)集合中的Model事件
集合能夠自動(dòng)偵聽(tīng)并轉(zhuǎn)發(fā)集合中的元素的事件,還有一些事件集合會(huì)做相應(yīng)的特殊處理,這些事件包括:
destroy 偵聽(tīng)到元素的destroy事件后,會(huì)自動(dòng)將元素從集合中移除,并引發(fā)remove事件
change:id 偵聽(tīng)到元素的id屬性被change后,自動(dòng)更新內(nèi)部對(duì)model的引用關(guān)系
自動(dòng)模型構(gòu)造
利用Collection的fetch,可以加載服務(wù)端數(shù)據(jù)集合,與此同時(shí),可以自動(dòng)創(chuàng)建相關(guān)的Model實(shí)例,并調(diào)用構(gòu)造方法
元素重復(fù)判斷
Collection會(huì)根據(jù)Model的idAttribute指定的唯一鍵,來(lái)判斷元素是否重復(fù),默認(rèn)情況下唯一鍵是id,可以重寫(xiě)idAttribute來(lái)覆蓋。當(dāng)元素重復(fù)的時(shí)候,可以選擇是丟棄重復(fù)元素,還是合并兩種元素,默認(rèn)是丟棄的
模型轉(zhuǎn)化
有時(shí)從REST接口得到的數(shù)據(jù)并不能完全滿足界面的處理需求,可以通過(guò)Model.parse或者Collection.parse方法,在實(shí)例化Backbone對(duì)象前,對(duì)數(shù)據(jù)進(jìn)行預(yù)處理。大體上,Model.parse用來(lái)對(duì)返回的單個(gè)對(duì)象進(jìn)行屬性的處理,而Collection.parse用來(lái)對(duì)返回的集合進(jìn)行處理,通常是過(guò)濾掉不必要的數(shù)據(jù)。例如:
//只挑選type=1的book var Books = Backbone.Collection.extend({ parse:function(models,options){ return _.filter(models , function(model){ return model.type == 1; }) } }) //為Book對(duì)象添加url屬性,以便渲染 var Book = Backbone.Model.extend({ parse: function(model,options){ return _.extend(model,{ url : '/books/' + model.id }); } })
通過(guò)Collection的fetch,自動(dòng)實(shí)例化的Model,其parse也會(huì)被調(diào)用。
模型的默認(rèn)值
Model可以通過(guò)設(shè)置defaults屬性來(lái)設(shè)置默認(rèn)值,這很有用。因?yàn)椋瑹o(wú)論是模型還是集合,fetch數(shù)據(jù)都是異步的,而往往視圖的渲染確實(shí)很可能在數(shù)據(jù)到來(lái)前就進(jìn)行了,如果沒(méi)有默認(rèn)值的話,一些使用了模板引擎的視圖,在渲染的時(shí)候可能會(huì)出錯(cuò)。例如underscore自帶的視圖引擎,由于使用with(){}語(yǔ)法,會(huì)因?yàn)閷?duì)象缺乏屬性而報(bào)錯(cuò)。
視圖的el
Backbone的視圖對(duì)象十分簡(jiǎn)答,對(duì)于開(kāi)發(fā)者而言,僅僅關(guān)心一個(gè)el屬性即可。el屬性可以通過(guò)五種途徑給出,優(yōu)先級(jí)從高到低:
- 實(shí)例化View的時(shí)候,傳遞el
- 在類(lèi)中聲明el
- 實(shí)例化View的時(shí)候傳入tagName
- 在類(lèi)中聲明tagName
- 以上都沒(méi)有的情況下使用默認(rèn)的'div'
究竟如何選擇,取決于以下幾點(diǎn):
- 一般而言,如果模塊是公用模塊,在類(lèi)中不提供el,而是讓外部在實(shí)例化的時(shí)候傳入,這樣可以保持公共的View的獨(dú)立性,不至于依賴已經(jīng)存在的DOM元素
- tagName一般對(duì)于自成體系的View有用,比如table中的某行tr,ul中的某個(gè)li
- 有些DOM事件必須在html存在的情況下才能綁定成功,比如blur,對(duì)于這種View,只能選擇已經(jīng)存在的html
視圖類(lèi)還有幾個(gè)屬性可以導(dǎo)出,由外部初始化,它們是:
// List of view options to be merged as properties. var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName', 'events'];
內(nèi)存泄漏
事件機(jī)制可以很好的帶來(lái)代碼維護(hù)的便利,但是由于事件綁定會(huì)使對(duì)象之間的引用變得復(fù)雜和錯(cuò)亂,容易造成內(nèi)存泄漏。下面的寫(xiě)法就會(huì)造成內(nèi)存泄漏:
var Task = Backbone.Model.extend({}) var TaskView = Backbone.View.extend({ tagName: 'tr', template: _.template('<td><%= id %></td><td><%= summary %></td><td><%= description %></td>'), initialize: function(){ this.listenTo(this.model,'change',this.render); }, render: function(){ this.$el.html( this.template( this.model.toJSON() ) ); return this; } }) var TaskCollection = Backbone.Collection.extend({ url: 'http://api.test.clippererm.com/api/testtasks', model: Task, comparator: 'summary' }) var TaskCollectionView = Backbone.View.extend({ initialize: function(){ this.listenTo(this.collection, 'add',this.addOne); this.listenTo(this.collection, 'reset',this.render); }, addOne: function(task){ var view = new TaskView({ model : task }); this.$el.append(view.render().$el); }, render: function(){ var _this = this; //簡(jiǎn)單粗暴的將DOM清空 //在sort事件觸發(fā)的render調(diào)用時(shí),之前實(shí)例化的TaskView對(duì)象會(huì)泄漏 this.$el.empty(); this.collection.each(function(model){ _this.addOne(model); }) return this; } })
使用下面的測(cè)試代碼,并結(jié)合Chrome的堆內(nèi)存快照來(lái)證明:
var tasks = null; var tasklist = null; $(function () { // body... $('#start').click(function(){ tasks = new TaskCollection(); tasklist = new TaskCollectionView({ collection : tasks, el: '#tasklist' }) tasklist.render(); tasks.fetch(); }) $('#refresh').click(function(){ tasks.fetch({ reset : true }); }) $('#sort').click(function(){ //將偵聽(tīng)sort放在這里,避免第一次加載數(shù)據(jù)后的自動(dòng)排序,觸發(fā)的sort事件,以至于混淆 tasklist.listenToOnce(tasks,'sort',tasklist.render); tasks.sort(); }) })
點(diǎn)擊開(kāi)始,使用Chrome的'Profile'下的'Take Heap Snapshot'功能,查看當(dāng)前堆內(nèi)存情況,使用child類(lèi)型過(guò)濾,可以看到Backbone對(duì)象實(shí)例一共有10個(gè)(1+1+4+4):
之所以用child過(guò)濾,因?yàn)槲覀兊念?lèi)繼承自Backbone的類(lèi)型,而繼承使用了重寫(xiě)原型的方法,Backbone在繼承時(shí),使用的變量名為child,最后,child被返回出來(lái)了
點(diǎn)擊排序后,再次抓取快照,可以看到實(shí)例個(gè)數(shù)變成了14個(gè),這是因?yàn)?,在render過(guò)程中,又創(chuàng)建了4個(gè)新的TaskView,而之前的4個(gè)TaskView并沒(méi)有釋放(之所以是4個(gè)是因?yàn)橛涗浀臈l數(shù)是4)
再次點(diǎn)擊排序,再次抓取快照,實(shí)例數(shù)又增加了4個(gè),變成了18個(gè)!
那么,為什么每次排序后,之前的TaskView無(wú)法釋放呢。因?yàn)門(mén)askView的實(shí)例都會(huì)偵聽(tīng)model,導(dǎo)致model對(duì)新創(chuàng)建的TaskView的實(shí)例存在引用,所以舊的TaskView無(wú)法刪除,又創(chuàng)建了新的,導(dǎo)致內(nèi)存不斷上漲。而且由于引用存在于change事件的回調(diào)隊(duì)列里,model每次觸發(fā)change都會(huì)通知舊的TaskView實(shí)例,導(dǎo)致執(zhí)行很多無(wú)用的代碼。那么如何改進(jìn)呢?
修改TaskCollectionView:
var TaskCollectionView = Backbone.View.extend({ initialize: function(){ this.listenTo(this.collection, 'add',this.addOne); this.listenTo(this.collection, 'reset',this.render); //初始化一個(gè)view數(shù)組以跟蹤創(chuàng)建的view this.views =[] }, addOne: function(task){ var view = new TaskView({ model : task }); this.$el.append(view.render().$el); //將新創(chuàng)建的view保存起來(lái) this.views.push(view); }, render: function(){ var _this = this; //遍歷views數(shù)組,并對(duì)每個(gè)view調(diào)用Backbone的remove _.each(this.views,function(view){ view.remove().off(); }) //清空views數(shù)組,此時(shí)舊的view就變成沒(méi)有任何被引用的不可達(dá)對(duì)象了 //垃圾回收器會(huì)回收它們 this.views =[]; this.$el.empty(); this.collection.each(function(model){ _this.addOne(model); }) return this; } })
Backbone的View有一個(gè)remove方法,這個(gè)方法除了刪除View所關(guān)聯(lián)的DOM對(duì)象,還會(huì)阻斷事件偵聽(tīng),它通過(guò)在listenTo方法時(shí)記錄下來(lái)的那些被偵聽(tīng)對(duì)象(上文事件原理中提到),來(lái)使這些被偵聽(tīng)的對(duì)象刪除對(duì)自己的引用。在remove內(nèi)部使用事件基類(lèi)的stopListening完成這個(gè)動(dòng)作。
上面的代碼使用一個(gè)views數(shù)組來(lái)跟蹤新創(chuàng)建的TaskView對(duì)象,并在render的時(shí)候,依次調(diào)用這些視圖對(duì)象的remove,然后清空數(shù)組,這樣這些TaskView對(duì)象就能得到釋放。并且,除了調(diào)用remove,還調(diào)用了off,把視圖對(duì)象可能的被外部的偵聽(tīng)也斷開(kāi)。
事件驅(qū)動(dòng)模塊
自定義事件:自定義事件比較適合多人合作開(kāi)發(fā),因?yàn)槲覀冎?,函?shù)名如果一樣的話,那么后面的函數(shù)會(huì)覆蓋前面的,而事件在綁定的情況下是不會(huì)被覆蓋的。
<script type="text/javascript"> //自定義事件 var Mod = backbone.Model.extend({ defaults : { name : 'trigkit4'; }, initialization : function(){ //初始化構(gòu)造函數(shù) this.on('change',function(){ //綁定change事件,當(dāng)數(shù)據(jù)改變時(shí)執(zhí)行此回調(diào)函數(shù) alert(123); }); } }); var model = new Mod; model.set('name' ,'backbone');//修改默認(rèn)的name屬性值為backbone,此時(shí)數(shù)據(jù)被改變,彈出123 </script>
事件綁定
除此之外,我們還可以自定義要綁定的被改變的數(shù)據(jù)類(lèi)型:
object.on(event, callback, [context])
綁定一個(gè)回調(diào)函數(shù)到一個(gè)對(duì)象上, 當(dāng)事件觸發(fā)時(shí)執(zhí)行回調(diào)函數(shù) :
<script type="text/javascript"> //自定義事件 var Mod = backbone.Model.extend({ defaults : { name : 'trigkit4', age : 21; }, initialization : function(){ //初始化構(gòu)造函數(shù) this.on('change:age',function(){ //綁定change事件,當(dāng)數(shù)據(jù)改變時(shí)執(zhí)行此回調(diào)函數(shù) alert(123); }); } }); var model = new Mod; model.set('name' ,'backbone');//修改默認(rèn)的name屬性值為backbone,此時(shí)數(shù)據(jù)被改變,彈出123 </script> listenTo <script type="text/javascript"> $(function(){ var Mod = Backbone.Model.extend({ defaults : { name : 'trigkit4' } }); var V = Backbone.View.extend({ initialize : function(){ this.listenTo(this.model,'change',this.show);//listenTo比on多了個(gè)參數(shù) }, show : function(model){ $('body').append('<div>' + model.get('name') + '</div>'); } }); var m = new Mod; var v = new V({model:m});//model指定創(chuàng)建的模型對(duì)象m,即前面的路由,哈希值的對(duì)應(yīng) m.set('name','hello');//對(duì)模型進(jìn)行就改時(shí),觸發(fā)事件,頁(yè)面也就更新了 }); </script>
istenTo
<script type="text/javascript"> $(function(){ var Mod = Backbone.Model.extend({ defaults : { name : 'trigkit4' } }); var V = Backbone.View.extend({ initialize : function(){ this.listenTo(this.model,'change',this.show);//listenTo比on多了個(gè)參數(shù) }, show : function(model){ $('body').append('<div>' + model.get('name') + '</div>'); } }); var m = new Mod; var v = new V({model:m});//model指定創(chuàng)建的模型對(duì)象m,即前面的路由,哈希值的對(duì)應(yīng) m.set('name','hello');//對(duì)模型進(jìn)行就改時(shí),觸發(fā)事件,頁(yè)面也就更新了 }); </script>
模型集合器
Backbone.Collection
集合是模型的有序組合,我們可以在集合上綁定 "change" 事件,從而當(dāng)集合中的模型發(fā)生變化時(shí)獲得通知,集合也可以監(jiān)聽(tīng) "add" 和 “remove" 事件, 從服務(wù)器更新,并能使用 Underscore.js 提供的方法
路由與歷史管理
<script type="text/javascript"> var Workspace = Backbone.Router.extend({ routes: { "help": "help", "search/:query": "search", "search/:query/p:page":" search" }, help : function(){ alert(123); }, search : function(query,page){ alert(345); } }); var w = new Workspace; Backbone.history.start();//backbone通過(guò)hash值找到對(duì)應(yīng)的回調(diào)函數(shù) </script> 事件委托 <script type="text/javascript"> $(function(){ var V = Backbone.View.extend({ el : $('body'), //對(duì)events進(jìn)行集體操作 events : { "click input" : "hello", "mouseover li" : "world" }, hello : function(){ alert(1234); }, world : function(){ alert(123) } }); var view = new V; }); </script> <body> <imput type = "button" value = "hwx" /> <ul> <li>1234</li> <li>1234</li> <li>1234</li> <li>1234</li> <li>1234</li> </ul> </body>
事件委托 格式:事件 + 空格 + 由誰(shuí)來(lái)觸發(fā) : 對(duì)應(yīng)的回調(diào)函數(shù)
相關(guān)文章
關(guān)于backbone url請(qǐng)求中參數(shù)帶有中文存入數(shù)據(jù)庫(kù)是亂碼的快速解決辦法
這篇文章主要介紹了關(guān)于backbone url請(qǐng)求中參數(shù)帶有中文存入數(shù)據(jù)庫(kù)是亂碼的快速解決辦法的相關(guān)資料,需要的朋友可以參考下2016-06-06JavaScript的Backbone.js框架入門(mén)學(xué)習(xí)指引
這篇文章主要介紹了JavaScript的Backbone.js框架入門(mén)學(xué)習(xí)指引, 其中特別講到了Backbone中的關(guān)鍵部分Router路由器,需要的朋友可以參考下2016-05-05輕量級(jí)javascript 框架Backbone使用指南
這篇文章主要介紹了輕量級(jí)javascript 框架Backbone使用指南的相關(guān)資料,需要的朋友可以參考下2015-07-07BackBone及其實(shí)例探究_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
這篇文章主要介紹了BackBone及其實(shí)例探究,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-07-07深入解析JavaScript框架Backbone.js中的事件機(jī)制
這篇文章主要介紹了JavaScript框架Backbone.js中的事件機(jī)制,其中涉及到Backbone的MVC結(jié)構(gòu)及內(nèi)存使用方面的很多知識(shí),需要的朋友可以參考下2016-02-02require、backbone等重構(gòu)手機(jī)圖片查看器
這篇文章主要為大家詳細(xì)介紹了require、backbone等重構(gòu)手機(jī)圖片查看器的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-11-11JavaScript的Backbone.js框架環(huán)境搭建及Hellow world示例
這篇文章主要介紹了JavaScript的Backbone.js框架環(huán)境搭建及Hellow world示例,Backbone是一個(gè)類(lèi)似MVC結(jié)構(gòu)的前端MVVM框架,非常輕量,需要的朋友可以參考下2016-05-05簡(jiǎn)單了解Backbone.js的Model模型以及View視圖的源碼
這篇文章主要簡(jiǎn)單介紹了Backbone.js的Model模型以及View視圖的源碼,Backbone是一款高人氣JavaScript的MVC框架,需要的朋友可以參考下2016-02-02backbone簡(jiǎn)介_(kāi)動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
這篇文章主要介紹了backbone簡(jiǎn)介,詳細(xì)的介紹了backbone簡(jiǎn)介和用法,有興趣的可以了解一下2017-07-07