帶你讀Backbone源碼解讀之Events實現

Backbone源碼解讀

Backbone在流行的前端框架中是最輕量級的一個,所有代碼實現一共只有1831行1。從前端的入門再到Titanium,我雖然幾回和Backbone打交道可是卻對它的結構知之甚少,也促成了我想讀它的代碼的原始動力。這個系列的文章主要目的是分享Backbone框架中能夠用於平常開發的實踐參考,力求可以簡明扼要的展示Backbone Modal, Controller和Sync這些核心內容,但願可以對你們學習和使用Backbone有一些幫助。前端

這個系列的文章將包括如下3篇內容:後端

  • Backbone源碼解讀之Events實現
  • Backbone源碼解讀之Router, History實現
  • Backbone源碼解讀之Model, Collection, Sync實現

本文是它的第一篇《Backbone源碼解讀之Events實現》數組

Backbone Events實現

Backbone的Event是整個框架運轉的齒輪。它的優美之處在於它是Backbone的一個基礎方法,經過_.extend的方法Mixin到Backbone的每個模塊中。前端框架

//Model
_.extend(Model.prototype, Events, {
    changed: null,

    //other Model prototype methods
    //...
}

Event的基礎概念

事件的管理是一個綁定在Events這個命名空間中的_events對象來實現的。
它的結構是name: [callback functions]的key-callback_array鍵值對。架構

this._events = {
    change: [callback_on_change1, callback_on_change2, ....],
    ....
}

當事件發生的時候,event從這個對象中根據事件的名稱取得回調函數數組,而後循環執行每一個回調函數,也就說明了爲何屢次綁定會重複觸發屢次事件。app

Event包括on, off, trigger三個基礎方法,其他的全部方法均是對它們的擴展。框架

on(name, callback, context)

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。

off(name, callback, context)

與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)

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;
    }
};

最簡單的Backbone事件使用

從使用上來說,對一個對象進行事件的on綁定。而後在同一個對象的其餘函數執行過程當中,或者其餘的對象中,觸發該對象的trigger方法和對應的事件名稱來執行其上綁定的callback函數。

其餘輔助函數

接下來再看看Event中進一步定義了哪些其餘輔助函數

once(name, callback, context)

效果相同與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這個屬性和用戶傳入的函數匹配,一樣能夠取消綁定。

listenTo(obj, name, callback)、listenToOnce(obj, name, callback)和stopListening(obj, name, 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本身來控制何時移除回調的執行,也可讓代碼的解耦程度更高。

超越Backbone

使用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結構。


  1. 根據2015年4月 穩定版本Backbone.js 1.1.2的註釋版本。Master上的代碼和註釋版本稍有出入,哪位大神知道爲何嗎?? 

相關文章
相關標籤/搜索