Backbone在流行的前端框架中是最輕量級的一個,所有代碼實現一共只有1831行1。從前端的入門再到Titanium,我雖然幾回和Backbone打交道可是卻對它的結構知之甚少,也促成了我想讀它的代碼的原始動力。這個系列的文章主要目的是分享Backbone框架中能夠用於平常開發的實踐參考,力求可以簡明扼要的展示Backbone Modal, Controller和Sync這些核心內容,但願可以對你們學習和使用Backbone有一些幫助。前端
這個系列的文章將包括如下3篇內容:後端
本文是它的第一篇《Backbone源碼解讀之Events實現》數組
Backbone的Event是整個框架運轉的齒輪。它的優美之處在於它是Backbone的一個基礎方法,經過_.extend的方法Mixin到Backbone的每個模塊中。前端框架
//Model _.extend(Model.prototype, Events, { changed: null, //other Model prototype methods //... }
事件的管理是一個綁定在Events這個命名空間中的_events對象來實現的。
它的結構是name: [callback functions]的key-callback_array鍵值對。架構
this._events = { change: [callback_on_change1, callback_on_change2, ....], .... }
當事件發生的時候,event從這個對象中根據事件的名稱取得回調函數數組,而後循環執行每一個回調函數,也就說明了爲何屢次綁定會重複觸發屢次事件。app
Event包括on, off, trigger三個基礎方法,其他的全部方法均是對它們的擴展。框架
on接受3個參數,包括事件的名稱,回調函數和回調函數執行的上下文環境。其中context是可選參數,若是你不是很熟悉JS的執行上下文環境能夠暫時不用管它。異步
拋開全部Backbone的花哨的檢查,執行on的操做本質就是向_events中name對應的回調函數數組[callback functions]中Push新的函數。
簡單來講代碼實現就是這個樣子:函數
Events.on = function(name, callback, context) { if (callback) { var handlers = events[name] || (events[name] = []); handlers.push({callback: callback, context: context, ctx: context || this}); } return this; };
至於你在看源代碼的時候會長不少,那是由於一方面Backbone要處理關於_events以及_events[name]未初始化的兩種特殊狀況。另外一方面eventsApi,onApi這些方法是爲了處理on時候你傳入的不是一個string類型的名稱和一個callback函數所作的條件處理。
例以下面兩種方法都是合法的:學習
//傳入一個名稱,回調函數的對象 model.on({ "change": on_change_callback, "remove": on_remove_callback }); //使用空格分割的多個事件名稱綁定到同一個回調函數上 model.on("change remove", common_callback);
可是核心其實都是同一個綁定函數。
值得注意的一點是因爲Backbone接受all做爲name的參數,而且將回調函數保存在_events.all中,關於它的執行詳細能夠參考trigger。
與on不一樣,off的3個參數都是可選的。
若是沒有任何參數的時候,off至關於把對應的_events對象總體清空。
if (!name && !callback && !context) { this._events = void 0; return this; }
若是有name參數可是沒有具體清除哪一個callback的時候,則把_events[name]對應的內容所有清空。
if (!callback && !context) { delete this._events[name]; continue; }
若是還有進一步詳細的callback和context的狀況下,則進入[callback functions]中進行檢查,移除具體一個回調函數的條件很是嚴苛,必需要求上下文和函數與原來徹底一致,也就是說若是傳入的callback或者context不是原有on對象的引用,而是複製的話,這裏的off是無效的。
var remaining = []; if( callback && callback !== handler.callback && callback !== handler.callback._callback || context && context !== handler.context ){ //保留回調函數在數組中 }
trigger取出name對應的_events[name]以及_event.all中的callback函數。
須要注意的一點是,觸發對應名稱的callback和all的callback使用了不同的參數,all的參數中還包含了當前事件的名稱。
//當綁定3個如下回調函數的時候Backbone會作以下優化處理,聽說這樣是能夠提升執行效率的。 var triggerEvents = function(events, args) { var ev, i = -1, l = events.length, a1 = args[0], a2 = args[1], a3 = args[2]; switch (args.length) { case 0: while (++i < l) (ev = events[i]).callback.call(ev.ctx); return; case 1: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1); return; case 2: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2); return; case 3: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2, a3); return; default: while (++i < l) (ev = events[i]).callback.apply(ev.ctx, args); return; } };
從使用上來說,對一個對象進行事件的on綁定。而後在同一個對象的其餘函數執行過程當中,或者其餘的對象中,觸發該對象的trigger方法和對應的事件名稱來執行其上綁定的callback函數。
接下來再看看Event中進一步定義了哪些其餘輔助函數
效果相同與on,不過對應的callback函數僅執行一次。固然,你一樣能夠在once綁定的回調函數執行前手動經過off將其移除。
來看看Once的實現,因爲添加了執行過程當中的移除方法,once在實際實行on的時候使用了以下匿名函數:
var once = _.once(function() {function(){ self.off(name, once); callback.apply(this, arguments); }); return this.on(name, once, context);
可是細心的你必定發現,保存在_event數組中的函數是once這個匿名函數了。可是用戶並不知道Backbone的這些操做,在取消綁定時仍然會使用原來的回調函數來試圖解除綁定。上面咱們也提到,必須使用徹底一致的函數纔可以取消綁定,那麼爲何還可以成功呢?
這裏Backbone作了一個小小的操做,不知道你有沒有注意到上面off函數中有這樣一行內容?
callback !== handler.callback._callback
既然callback是咱們傳入的回調函數,那麼哪裏來的_callback這個屬性呢?答案就在once裏面。
var once = _.once(function() {...取消綁定,執行callback); once._callback = callback;
也就是Backbone在返回以前悄悄的爲once這個函數添加了一個_callback的屬性,用來保存原來的回調函數,這樣用戶在傳入原來的回調函數取消綁定的時候,off會檢查函數時候有_callback這個屬性和用戶傳入的函數匹配,一樣能夠取消綁定。
除了將對象自己expose給另外一個對象,讓另外一個對象執行trigger方法觸發該對象上綁定的event之外。Event還進一步提供了listenTo系列的方法,執行邏輯正好與on相反。
例若有以下要求,當B對象上發生事件b的時候,觸發A對象的callbackOnBEvent函數。
// 使用on的狀況下 B.on(「b」, A.callbackOnBEvent) // 使用listenTo的狀況下 A.listenTo(B, 「b」, callbackOnEvent);
從實現上看,它門的區別就在於誰負責管理這個事件。第一個模型中,B就像是整個系統的master,負責事件到達的時候的分發,讓不一樣的對象(如A)執行對應的方法。第二個模型中,B更像是一個信息棧,A監聽B上發生的事件,而且在對應事件到達的時候觸發自身相應的回調函數。二者並沒有好壞之分,可是從系統架構上來講由於自己回調函數的上下文環境就是A,因此listenTo的方式可能會來的更加天然,並且由A本身來控制何時移除回調的執行,也可讓代碼的解耦程度更高。
使用Event方法來處理異步請求讓代碼的可讀性大大增長。若是你的單頁面應用剛好使用了Backbone做爲前端框架,將Event經過Backbone.Events這個變量暴露出來,你可使用相似Model擴展的方法
//Your object _.extend(your_object.prototype, Backbone.Events, { //other prototype methods //... }
這樣你的Object也就具備了彼此綁定事件、觸發事件的能力。
即使你的前端並無使用Backbone,因爲Events並不依賴Backbone的其餘部分實現,你徹底能夠將它放到本身的代碼lib中,做爲一個基礎方法來使用。
相似的方式你也能夠常常在Node的後端看到
var util = require("util"); var events = require("events"); function MyStream() { events.EventEmitter.call(this); } util.inherits(MyStream, events.EventEmitter);
總之,我我的是很是推薦多多使用Event來替代層級的Callback結構。
根據2015年4月 穩定版本Backbone.js 1.1.2的註釋版本。Master上的代碼和註釋版本稍有出入,哪位大神知道爲何嗎?? ↩