国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > JavaScript > 正文

深入解析JavaScript框架Backbone.js中的事件機制

2019-11-20 10:36:08
字體:
來源:轉載
供稿:網友


事件模型及其原理
Backbone.Events就是事件實現的核心,它可以讓對象擁有事件能力

var Events = Backbone.Events = { .. }

對象通過listenTo偵聽其他對象,通過trigger觸發事件??梢悦撾xBackbone的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');

執行結果:

2016214153156830.jpg (644×434)

Backbone的Model和View等核心類,都是繼承自Backbone.Events的。例如Backbone.Model:

var Events = Backbone.Events = { .. }var Model = Backbone.Model = function(attributes, options) { ...};_.extend(Model.prototype, Events, { ... })

從原理上講,事件是這么工作的:

被偵聽的對象維護一個事件數組_event,其他對象在調用listenTo時,會將事件名與回調維護到隊列中:

2016214153253355.png (367×193)

一個事件名可以對應多個回調,對于被偵聽者而言,只知道回調的存在,并不知道具體是哪個對象在偵聽它。當被偵聽者調用trigger(name)時,會遍歷_event,選擇同名的事件,并將其下面所有的回調都執行一遍。

需要額外注意的是,Backbone的listenTo實現,除了使被偵聽者維護對偵聽者的引用外,還使偵聽者也維護了被偵聽者。這是為了在恰當的時候,偵聽者可以單方面中斷偵聽。因此,雖然是循環引用,但是使用Backbone的合適的方法可以很好的維護,不會有問題,在后面的內存泄露部分將看到。

另外,有時只希望事件在綁定后,當回調發生后,就接觸綁定。這在一些對公共模塊的引用時很有用。listenToOnce可以做到這一點

與服務器同步數據
backbone默認實現了一套與RESTful風格的服務端同步模型的機制,這套機制不僅可以減輕開發人員的工作量,而且可以使模型變得更為健壯(在各種異常下仍能保持數據一致性)。不過,要真正發揮這個功效,一個與之匹配的服務端實現是很重要的。為了說明問題,假設服務端有如下REST風格的接口:

  • GET /resources 獲取資源列表
  • POST /resources 創建一個資源,返回資源的全部或部分字段
  • GET /resources/{id} 獲取某個id的資源詳情,返回資源的全部或部分字段
  • DELETE /resources/{id} 刪除某個資源
  • PUT /resources/{id} 更新某個資源的全部字段,返回資源的全部或部分字段
  • PATCH /resources/{id} 更新某個資源的部分字段,返回資源的全部或部分字段

backbone會使用到上面這些HTTP方法的地方主要有以下幾個:

  • Model.save() 邏輯上,根據當前這個model的是否具有id來判斷應該使用POST還是PUT,如果model沒有id,表示是新的模型,將使用POST,將模型的字段全部提交到/resources;如果model具有id,表示是已經存在的模型,將使用PUT,將模型的全部字段提交到/resources/{id}。當傳入options包含patch:true的時候,save會產生PATCH。
  • Model.destroy() 會產生DELETE,目標url為/resources/{id},如果當前model不包含id時,不會與服務端同步,因為此時backbone認為model在服務端尚不存在,不需要刪除
  • Model.fetch() 會產生GET,目標url為/resources/{id},并將獲得的屬性更新model。
  • Collection.fetch() 會產生GET,目標url為/resources,并對返回的數組中的每個對象,自動實例化model
  • Collection.create() 實際將調用Model.save

options參數存在于上面任何一個方法的參數列表中,通過options可以修改backbone和ajax請求的一些行為,可以使用的options包括:

  • wait: 可以指定是否等待服務端的返回結果再更新model。默認情況下不等待
  • url: 可以覆蓋掉backbone默認使用的url格式
  • attrs: 可以指定保存到服務端的字段有哪些,配合options.patch可以產生PATCH對模型進行部分更新
  • patch: 指定使用部分更新的REST接口
  • data: 會被直接傳遞給jquery的ajax中的data,能夠覆蓋backbone所有的對上傳的數據控制的行為
  • 其他: options中的任何參數都將直接傳遞給jquery的ajax,作為其options

backbone通過Model的urlRoot屬性或者是Collection的url屬性得知具體的服務端接口地址,以便發起ajax。在Model的url默認實現中,Model除了會考察urlRoot,第二選擇會是Model所在Collection的url,所有有時只需要在Collection里面書寫url就可以了。

Backbone會根據與服務端要進行什么類型的操作,決定是否要添加id在url后面,以下代碼是Model的默認url實現:

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最后是否包含'/'的不確定性。

這個正則匹配的是行末的非/字符,這樣,像/resources這樣的目標會匹配s,然后replace中使用分組編號$1捕獲了s,將s替換為s/,這樣就自動加上了缺失的/;而當/resources/這樣目標卻無法匹配到結果,也就不需要替換了。
Model和Collection的關系
在backbone中,即便一類的模型實例的確是在一個集合里面,也并沒有強制要求使用集合類。但是使用集合有一些額外的好處,這些好處包括:

url繼承
Model屬于Collection后,可以繼承Collection的url屬性。Collection沿用了underscore90%的集合和數組操作,使得集合操作極其方便:

// 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); //將參數數組轉化成真正的數組 args.unshift(this.models);  //將Collection真正用來維護集合的數組,作為第一個個參數 return _[method].apply(_, args); //使用apply調用underscore的方法 };});

自動偵聽和轉發集合中的Model事件
集合能夠自動偵聽并轉發集合中的元素的事件,還有一些事件集合會做相應的特殊處理,這些事件包括:

destroy 偵聽到元素的destroy事件后,會自動將元素從集合中移除,并引發remove事件
change:id 偵聽到元素的id屬性被change后,自動更新內部對model的引用關系
自動模型構造
利用Collection的fetch,可以加載服務端數據集合,與此同時,可以自動創建相關的Model實例,并調用構造方法

元素重復判斷
Collection會根據Model的idAttribute指定的唯一鍵,來判斷元素是否重復,默認情況下唯一鍵是id,可以重寫idAttribute來覆蓋。當元素重復的時候,可以選擇是丟棄重復元素,還是合并兩種元素,默認是丟棄的

模型轉化
有時從REST接口得到的數據并不能完全滿足界面的處理需求,可以通過Model.parse或者Collection.parse方法,在實例化Backbone對象前,對數據進行預處理。大體上,Model.parse用來對返回的單個對象進行屬性的處理,而Collection.parse用來對返回的集合進行處理,通常是過濾掉不必要的數據。例如:

//只挑選type=1的bookvar 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也會被調用。

模型的默認值
Model可以通過設置defaults屬性來設置默認值,這很有用。因為,無論是模型還是集合,fetch數據都是異步的,而往往視圖的渲染確實很可能在數據到來前就進行了,如果沒有默認值的話,一些使用了模板引擎的視圖,在渲染的時候可能會出錯。例如underscore自帶的視圖引擎,由于使用with(){}語法,會因為對象缺乏屬性而報錯。

視圖的el
Backbone的視圖對象十分簡答,對于開發者而言,僅僅關心一個el屬性即可。el屬性可以通過五種途徑給出,優先級從高到低:

  • 實例化View的時候,傳遞el
  • 在類中聲明el
  • 實例化View的時候傳入tagName
  • 在類中聲明tagName
  • 以上都沒有的情況下使用默認的'div'

究竟如何選擇,取決于以下幾點:

  • 一般而言,如果模塊是公用模塊,在類中不提供el,而是讓外部在實例化的時候傳入,這樣可以保持公共的View的獨立性,不至于依賴已經存在的DOM元素
  • tagName一般對于自成體系的View有用,比如table中的某行tr,ul中的某個li
  • 有些DOM事件必須在html存在的情況下才能綁定成功,比如blur,對于這種View,只能選擇已經存在的html

視圖類還有幾個屬性可以導出,由外部初始化,它們是:

// List of view options to be merged as properties.var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName', 'events'];

內存泄漏
事件機制可以很好的帶來代碼維護的便利,但是由于事件綁定會使對象之間的引用變得復雜和錯亂,容易造成內存泄漏。下面的寫法就會造成內存泄漏:

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; //簡單粗暴的將DOM清空 //在sort事件觸發的render調用時,之前實例化的TaskView對象會泄漏 this.$el.empty(); this.collection.each(function(model){  _this.addOne(model); }) return this; }})

使用下面的測試代碼,并結合Chrome的堆內存快照來證明:

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放在這里,避免第一次加載數據后的自動排序,觸發的sort事件,以至于混淆 tasklist.listenToOnce(tasks,'sort',tasklist.render); tasks.sort(); })})

點擊開始,使用Chrome的'Profile'下的'Take Heap Snapshot'功能,查看當前堆內存情況,使用child類型過濾,可以看到Backbone對象實例一共有10個(1+1+4+4):

2016214153540966.jpg (929×520)

之所以用child過濾,因為我們的類繼承自Backbone的類型,而繼承使用了重寫原型的方法,Backbone在繼承時,使用的變量名為child,最后,child被返回出來了
點擊排序后,再次抓取快照,可以看到實例個數變成了14個,這是因為,在render過程中,又創建了4個新的TaskView,而之前的4個TaskView并沒有釋放(之所以是4個是因為記錄的條數是4)

2016214153605533.jpg (940×588)

再次點擊排序,再次抓取快照,實例數又增加了4個,變成了18個!

2016214153626639.jpg (934×614)

那么,為什么每次排序后,之前的TaskView無法釋放呢。因為TaskView的實例都會偵聽model,導致model對新創建的TaskView的實例存在引用,所以舊的TaskView無法刪除,又創建了新的,導致內存不斷上漲。而且由于引用存在于change事件的回調隊列里,model每次觸發change都會通知舊的TaskView實例,導致執行很多無用的代碼。那么如何改進呢?

修改TaskCollectionView:

var TaskCollectionView = Backbone.View.extend({ initialize: function(){ this.listenTo(this.collection, 'add',this.addOne); this.listenTo(this.collection, 'reset',this.render); //初始化一個view數組以跟蹤創建的view this.views =[] }, addOne: function(task){ var view = new TaskView({ model : task }); this.$el.append(view.render().$el); //將新創建的view保存起來 this.views.push(view); }, render: function(){ var _this = this; //遍歷views數組,并對每個view調用Backbone的remove _.each(this.views,function(view){  view.remove().off(); }) //清空views數組,此時舊的view就變成沒有任何被引用的不可達對象了 //垃圾回收器會回收它們 this.views =[]; this.$el.empty(); this.collection.each(function(model){  _this.addOne(model); }) return this; }})

Backbone的View有一個remove方法,這個方法除了刪除View所關聯的DOM對象,還會阻斷事件偵聽,它通過在listenTo方法時記錄下來的那些被偵聽對象(上文事件原理中提到),來使這些被偵聽的對象刪除對自己的引用。在remove內部使用事件基類的stopListening完成這個動作。
上面的代碼使用一個views數組來跟蹤新創建的TaskView對象,并在render的時候,依次調用這些視圖對象的remove,然后清空數組,這樣這些TaskView對象就能得到釋放。并且,除了調用remove,還調用了off,把視圖對象可能的被外部的偵聽也斷開。

事件驅動模塊
自定義事件:自定義事件比較適合多人合作開發,因為我們知道,函數名如果一樣的話,那么后面的函數會覆蓋前面的,而事件在綁定的情況下是不會被覆蓋的。

<script type="text/javascript"> //自定義事件 var Mod = backbone.Model.extend({ defaults : {  name : 'trigkit4'; }, initialization : function(){ //初始化構造函數  this.on('change',function(){ //綁定change事件,當數據改變時執行此回調函數  alert(123);  }); } }); var model = new Mod; model.set('name' ,'backbone');//修改默認的name屬性值為backbone,此時數據被改變,彈出123</script>


事件綁定
除此之外,我們還可以自定義要綁定的被改變的數據類型:

object.on(event, callback, [context])

綁定一個回調函數到一個對象上, 當事件觸發時執行回調函數 :

<script type="text/javascript"> //自定義事件 var Mod = backbone.Model.extend({ defaults : {  name : 'trigkit4',  age : 21; }, initialization : function(){ //初始化構造函數  this.on('change:age',function(){ //綁定change事件,當數據改變時執行此回調函數  alert(123);  }); } }); var model = new Mod; model.set('name' ,'backbone');//修改默認的name屬性值為backbone,此時數據被改變,彈出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多了個參數  },  show : function(model){  $('body').append('<div>' + model.get('name') + '</div>');  } }); var m = new Mod; var v = new V({model:m});//model指定創建的模型對象m,即前面的路由,哈希值的對應 m.set('name','hello');//對模型進行就改時,觸發事件,頁面也就更新了  });</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多了個參數   },   show : function(model){    $('body').append('<div>' + model.get('name') + '</div>');   }  });  var m = new Mod;  var v = new V({model:m});//model指定創建的模型對象m,即前面的路由,哈希值的對應  m.set('name','hello');//對模型進行就改時,觸發事件,頁面也就更新了   });</script>

模型集合器
Backbone.Collection
集合是模型的有序組合,我們可以在集合上綁定 "change" 事件,從而當集合中的模型發生變化時獲得通知,集合也可以監聽 "add" 和 “remove" 事件, 從服務器更新,并能使用 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通過hash值找到對應的回調函數</script>事件委托  <script type="text/javascript">    $(function(){      var V = Backbone.View.extend({        el : $('body'),        //對events進行集體操作        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>

事件委托 格式:事件 + 空格 + 由誰來觸發 : 對應的回調函數

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 沛县| 贵州省| 绍兴市| 武乡县| 南康市| 哈巴河县| 西充县| 灵石县| 溧水县| 望谟县| 峨山| 东源县| 灵川县| 崇义县| 长兴县| 阿克陶县| 库车县| 曲靖市| 扎赉特旗| 泌阳县| 昭通市| 古丈县| 金堂县| 哈尔滨市| 察隅县| 琼海市| 汉川市| 分宜县| 咸宁市| 疏附县| 民勤县| 英吉沙县| 清苑县| 读书| 哈密市| 观塘区| 武平县| 保靖县| 桑植县| 丰顺县| 铁力市|