摘要:原文使用總結事件模型及其原理就是事件實現(xiàn)的核心,它可以讓對象擁有事件能力對象通過偵聽其他對象,通過觸發(fā)事件。可以脫離的,在自定義的對象上使用事件執(zhí)行結果的和等核心類,都是繼承自的。在內(nèi)部使用事件基類的完成這個動作。
開始在項目中大規(guī)模使用backbone,一路磕磕碰碰,邊做邊學習邊體會,有一些心得和體會,記錄在本文中。原文:Backbone使用總結
事件模型及其原理Backbone.Events就是事件實現(xiàn)的核心,它可以讓對象擁有事件能力
var Events = Backbone.Events = { .. }
對象通過listenTo偵聽其他對象,通過trigger觸發(fā)事件。可以脫離Backbone的MVC,在自定義的對象上使用事件
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í)行結果:
Backbone的Model和View等核心類,都是繼承自Backbone.Events的。例如Backbone.Model:
var Events = Backbone.Events = { .. } var Model = Backbone.Model = function(attributes, options) { ... }; _.extend(Model.prototype, Events, { ... })
從原理上講,事件是這么工作的:
被偵聽的對象維護一個事件數(shù)組_event,其他對象在調(diào)用listenTo時,會將事件名與回調(diào)維護到隊列中:
一個事件名可以對應多個回調(diào),對于被偵聽者而言,只知道回調(diào)的存在,并不知道具體是哪個對象在偵聽它。當被偵聽者調(diào)用trigger(name)時,會遍歷_event,選擇同名的事件,并將其下面所有的回調(diào)都執(zhí)行一遍。
需要額外注意的是,Backbone的listenTo實現(xiàn),除了使被偵聽者維護對偵聽者的引用外,還使偵聽者也維護了被偵聽者。這是為了在恰當?shù)臅r候,偵聽者可以單方面中斷偵聽。因此,雖然是循環(huán)引用,但是使用Backbone的合適的方法可以很好的維護,不會有問題,在后面的內(nèi)存泄露部分將看到。
另外,有時只希望事件在綁定后,當回調(diào)發(fā)生后,就接觸綁定。這在一些對公共模塊的引用時很有用。listenToOnce可以做到這一點
與服務器同步數(shù)據(jù)backbone默認實現(xiàn)了一套與RESTful風格的服務端同步模型的機制,這套機制不僅可以減輕開發(fā)人員的工作量,而且可以使模型變得更為健壯(在各種異常下仍能保持數(shù)據(jù)一致性)。不過,要真正發(fā)揮這個功效,一個與之匹配的服務端實現(xiàn)是很重要的。為了說明問題,假設服務端有如下REST風格的接口:
GET /resources 獲取資源列表
POST /resources 創(chuàng)建一個資源,返回資源的全部或部分字段
GET /resources/{id} 獲取某個id的資源詳情,返回資源的全部或部分字段
DELETE /resources/{id} 刪除某個資源
PUT /resources/{id} 更新某個資源的全部字段,返回資源的全部或部分字段
PATCH /resources/{id} 更新某個資源的部分字段,返回資源的全部或部分字段
backbone會使用到上面這些HTTP方法的地方主要有以下幾個:
Model.save() 邏輯上,根據(jù)當前這個model的是否具有id來判斷應該使用POST還是PUT,如果model沒有id,表示是新的模型,將使用POST,將模型的字段全部提交到/resources;如果model具有id,表示是已經(jīng)存在的模型,將使用PUT,將模型的全部字段提交到/resources/{id}。當傳入options包含patch:true的時候,save會產(chǎn)生PATCH。
Model.destroy() 會產(chǎn)生DELETE,目標url為/resources/{id},如果當前model不包含id時,不會與服務端同步,因為此時backbone認為model在服務端尚不存在,不需要刪除
Model.fetch() 會產(chǎn)生GET,目標url為/resources/{id},并將獲得的屬性更新model。
Collection.fetch() 會產(chǎn)生GET,目標url為/resources,并對返回的數(shù)組中的每個對象,自動實例化model
Collection.create() 實際將調(diào)用Model.save
options參數(shù)存在于上面任何一個方法的參數(shù)列表中,通過options可以修改backbone和ajax請求的一些行為,可以使用的options包括:
wait: 可以指定是否等待服務端的返回結果再更新model。默認情況下不等待
url: 可以覆蓋掉backbone默認使用的url格式
attrs: 可以指定保存到服務端的字段有哪些,配合options.patch可以產(chǎn)生PATCH對模型進行部分更新
patch: 指定使用部分更新的REST接口
data: 會被直接傳遞給jquery的ajax中的data,能夠覆蓋backbone所有的對上傳的數(shù)據(jù)控制的行為
其他: options中的任何參數(shù)都將直接傳遞給jquery的ajax,作為其options
backbone通過Model的urlRoot屬性或者是Collection的url屬性得知具體的服務端接口地址,以便發(fā)起ajax。在Model的url默認實現(xiàn)中,Model除了會考察urlRoot,第二選擇會是Model所在Collection的url,所有有時只需要在Collection里面書寫url就可以了。
Backbone會根據(jù)與服務端要進行什么類型的操作,決定是否要添加id在url后面,以下代碼是Model的默認url實現(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); },
其中的正則式/([^/])$/是個很巧妙的處理,它解決了url最后是否包含"/"的不確定性。
Model和Collection的關系這個正則匹配的是行末的非/字符,這樣,像/resources這樣的目標會匹配s,然后replace中使用分組編號$1捕獲了s,將s替換為s/,這樣就自動加上了缺失的/;而當/resources/這樣目標卻無法匹配到結果,也就不需要替換了。
在backbone中,即便一類的模型實例的確是在一個集合里面,也并沒有強制要求使用集合類。但是使用集合有一些額外的好處,這些好處包括:
url繼承Model屬于Collection后,可以繼承Collection的url屬性。上面一節(jié)已經(jīng)提到了
underscore集合能力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ù)組轉化成真正的數(shù)組 args.unshift(this.models); //將Collection真正用來維護集合的數(shù)組,作為第一個個參數(shù) return _[method].apply(_, args); //使用apply調(diào)用underscore的方法 }; });自動偵聽和轉發(fā)集合中的Model事件
集合能夠自動偵聽并轉發(fā)集合中的元素的事件,還有一些事件集合會做相應的特殊處理,這些事件包括:
destroy 偵聽到元素的destroy事件后,會自動將元素從集合中移除,并引發(fā)remove事件
change:id 偵聽到元素的id屬性被change后,自動更新內(nèi)部對model的引用關系
自動模型構造利用Collection的fetch,可以加載服務端數(shù)據(jù)集合,與此同時,可以自動創(chuàng)建相關的Model實例,并調(diào)用構造方法
元素重復判斷Collection會根據(jù)Model的idAttribute指定的唯一鍵,來判斷元素是否重復,默認情況下唯一鍵是id,可以重寫idAttribute來覆蓋。當元素重復的時候,可以選擇是丟棄重復元素,還是合并兩種元素,默認是丟棄的
模型轉化有時從REST接口得到的數(shù)據(jù)并不能完全滿足界面的處理需求,可以通過Model.parse或者Collection.parse方法,在實例化Backbone對象前,對數(shù)據(jù)進行預處理。大體上,Model.parse用來對返回的單個對象進行屬性的處理,而Collection.parse用來對返回的集合進行處理,通常是過濾掉不必要的數(shù)據(jù)。例如:
//只挑選type=1的book var Books = Backbone.Collection.extend({ parse:function(models,options){ return _.filter(models , function(model){ return model.type == 1; }) } }) //為Book對象添加url屬性,以便渲染 var Book = Backbone.Model.extend({ parse: function(model,options){ return _.extend(model,{ url : "/books/" + model.id }); } })
通過Collection的fetch,自動實例化的Model,其parse也會被調(diào)用。
模型的默認值Model可以通過設置defaults屬性來設置默認值,這很有用。因為,無論是模型還是集合,fetch數(shù)據(jù)都是異步的,而往往視圖的渲染確實很可能在數(shù)據(jù)到來前就進行了,如果沒有默認值的話,一些使用了模板引擎的視圖,在渲染的時候可能會出錯。例如underscore自帶的視圖引擎,由于使用with(){}語法,會因為對象缺乏屬性而報錯。
視圖的elBackbone的視圖對象十分簡答,對于開發(fā)者而言,僅僅關心一個el屬性即可。el屬性可以通過五種途徑給出,優(yōu)先級從高到低:
實例化View的時候,傳遞el
在類中聲明el
實例化View的時候傳入tagName
在類中聲明tagName
以上都沒有的情況下使用默認的"div"
究竟如何選擇,取決于以下幾點:
一般而言,如果模塊是公用模塊,在類中不提供el,而是讓外部在實例化的時候傳入,這樣可以保持公共的View的獨立性,不至于依賴已經(jīng)存在的DOM元素
tagName一般對于自成體系的View有用,比如table中的某行tr,ul中的某個li
有些DOM事件必須在html存在的情況下才能綁定成功,比如blur,對于這種View,只能選擇已經(jīng)存在的html
視圖類還有幾個屬性可以導出,由外部初始化,它們是:
// List of view options to be merged as properties. var viewOptions = ["model", "collection", "el", "id", "attributes", "className", "tagName", "events"];內(nèi)存泄漏
事件機制可以很好的帶來代碼維護的便利,但是由于事件綁定會使對象之間的引用變得復雜和錯亂,容易造成內(nèi)存泄漏。下面的寫法就會造成內(nèi)存泄漏:
var Task = Backbone.Model.extend({}) var TaskView = Backbone.View.extend({ tagName: "tr", template: _.template("<%= id %> <%= summary %> <%= description %> "), 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; //簡單粗暴的將DOM清空 //在sort事件觸發(fā)的render調(diào)用時,之前實例化的TaskView對象會泄漏 this.$el.empty(); this.collection.each(function(model){ _this.addOne(model); }) return this; } })
使用下面的測試代碼,并結合Chrome的堆內(nè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(){ //將偵聽sort放在這里,避免第一次加載數(shù)據(jù)后的自動排序,觸發(fā)的sort事件,以至于混淆 tasklist.listenToOnce(tasks,"sort",tasklist.render); tasks.sort(); }) })
點擊開始,使用Chrome的"Profile"下的"Take Heap Snapshot"功能,查看當前堆內(nèi)存情況,使用child類型過濾,可以看到Backbone對象實例一共有10個(1+1+4+4):
之所以用child過濾,因為我們的類繼承自Backbone的類型,而繼承使用了重寫原型的方法,Backbone在繼承時,使用的變量名為child,最后,child被返回出來了
點擊排序后,再次抓取快照,可以看到實例個數(shù)變成了14個,這是因為,在render過程中,又創(chuàng)建了4個新的TaskView,而之前的4個TaskView并沒有釋放(之所以是4個是因為記錄的條數(shù)是4)
再次點擊排序,再次抓取快照,實例數(shù)又增加了4個,變成了18個!
那么,為什么每次排序后,之前的TaskView無法釋放呢。因為TaskView的實例都會偵聽model,導致model對新創(chuàng)建的TaskView的實例存在引用,所以舊的TaskView無法刪除,又創(chuàng)建了新的,導致內(nèi)存不斷上漲。而且由于引用存在于change事件的回調(diào)隊列里,model每次觸發(fā)change都會通知舊的TaskView實例,導致執(zhí)行很多無用的代碼。那么如何改進呢?
修改TaskCollectionView:
var TaskCollectionView = Backbone.View.extend({ initialize: function(){ this.listenTo(this.collection, "add",this.addOne); this.listenTo(this.collection, "reset",this.render); //初始化一個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保存起來 this.views.push(view); }, render: function(){ var _this = this; //遍歷views數(shù)組,并對每個view調(diào)用Backbone的remove _.each(this.views,function(view){ view.remove().off(); }) //清空views數(shù)組,此時舊的view就變成沒有任何被引用的不可達對象了 //垃圾回收器會回收它們 this.views =[]; this.$el.empty(); this.collection.each(function(model){ _this.addOne(model); }) return this; } })
Backbone的View有一個remove方法,這個方法除了刪除View所關聯(lián)的DOM對象,還會阻斷事件偵聽,它通過在listenTo方法時記錄下來的那些被偵聽對象(上文事件原理中提到),來使這些被偵聽的對象刪除對自己的引用。在remove內(nèi)部使用事件基類的stopListening完成這個動作。
上面的代碼使用一個views數(shù)組來跟蹤新創(chuàng)建的TaskView對象,并在render的時候,依次調(diào)用這些視圖對象的remove,然后清空數(shù)組,這樣這些TaskView對象就能得到釋放。并且,除了調(diào)用remove,還調(diào)用了off,把視圖對象可能的被外部的偵聽也斷開。
文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/107005.html
摘要:應用的功能這個應用是一個個人簡歷生成器。比較好的教程有這一個。這樣的命名污染問題自然顯而易見。而且發(fā)出多次請求也會影響性能。明顯不利于維護。然而我希望能夠不發(fā)生變化,因為是在文件的前提下的標簽頁,不能換一個標簽就重建一個。 為什么學習backbone?這是個好問題。在這個前端框架爆炸的年代,比起backbone,對開發(fā)來說有更多更好的選擇,react,vue,angular等等。但這些...
摘要:原文精髓,觀察者模式和事件交互邏輯更需要設計模式設計模式將人們在以往的開發(fā)過程中的經(jīng)驗加以總結,以指導后人。的事件根據(jù)上面討論,要實現(xiàn)觀察者模式,事件是非常重要的機制。總結雖然是模式的框架,但是其核心卻是界面的觀察者模式和事件機制。 前言 本人并非專業(yè)的前端,只是由于需要被迫轉做一段時間的前端,一段時間以來開始探索javascript上的MVC模式,最終打算從Backbone下手。在...
1. 開場 1.1 MVC? MVC是一種GUI軟件的一種架構模式。它的目的是將軟件的數(shù)據(jù)層(Model)和視圖(view)分開。Model連接數(shù)據(jù)庫,實現(xiàn)數(shù)據(jù)的交互。用戶不能直接和數(shù)據(jù)打交道,而是需要通過操作視圖,然后通過controller對事件作出響應,最后才得以改變數(shù)據(jù)。最后數(shù)據(jù)改變,通過觀察者模式更新view。(所以在這里需要用到設計模式中的觀察者模式) 1.2 Smalltalk-80...
摘要:所以大量的問題都留給開發(fā)者自己想辦法來解決,因此遭到吐槽當然,使用純開發(fā)一個復雜應用時,情況就會變得非常糟糕。管理復雜的交互自己維護。影響了集合的排列。在中調(diào)用這樣做是不對的,因為會讓應用越來越復雜的。 只扯蛋,不給代碼,就是耍流氓 -- honger。 完整的 tutorial 代碼 戳這里, 因為我使用的是 commonjs 規(guī)范,基于 spm 的,你可以先安裝,然后運行它。更多 ...
摘要:原文譯者飛龍協(xié)議這個例子展示了如何在的引擎中使用模型。在年三月首次作為的一部分發(fā)布,并通過以原生方式在上運行腳本擴展了的功能。將二者放在一起下一個目標是在中,例如在服務器上復用模型。最后,我們在中調(diào)用函數(shù)。總結在中復用現(xiàn)存的庫十分簡單。 原文:Using Backbone.js with Nashorn 譯者:飛龍 協(xié)議:CC BY-NC-SA 4.0 這個例子展示了如何在Java8...
閱讀 3186·2019-08-30 15:55
閱讀 2952·2019-08-30 13:46
閱讀 1454·2019-08-29 17:29
閱讀 3522·2019-08-29 11:08
閱讀 3446·2019-08-29 11:04
閱讀 1093·2019-08-28 18:20
閱讀 552·2019-08-26 13:37
閱讀 1335·2019-08-26 11:49