小而美的backbone

本文已同步在個人博客前端

在這個reactvue如日中天、jquery逐漸被你們拋棄的年代,我仍是想要來講一說backbonevue

16年6月初,在沒有任何前端框架使用經驗、js水平也較通常的狀況下,被告知須要在幾個工做日內搭建完成一個後臺管理系統,沒有頁面設計稿、沒有組件庫,一切都是從零開始。當時面臨兩個選擇,backbone和react。雖然我很但願可以拿react來練手,可是考慮到學習成本和項目時間問題,leader仍是建議我使用backbone來完成,就這樣,一直用到差很少如今。雖然到項目後期業務場景愈來愈複雜,backbone的這套技術棧體現出愈來愈多的問題,可是對於小型項目來講,我仍是認爲backbone是個不錯的選擇,並且學習成本低,上手極快~

backbone是個很是輕量的mvc庫,本文將基於backbone的源碼談一談其實現的核心部分,以及其中一些巧妙的思想和代碼實現技巧。react

事件機制(Events)

事件部分的核心邏輯其實比較簡單,簡化一下能夠用以下的僞代碼來表示:jquery

var events = {
    evt1: handlers1,
    evt2: handlers2,
    ...
}

//註冊事件
function on(name, callback) {
    const handlers = events[name] || (events[name] = []);
    handles.push(callback);
}

//觸發事件
function trigger(name) {
    if (!events[name]) {
        return;
    }
    const handlers = events[name];
    for (let i = 0, len = handlers.length; i < len; i++) {
        handlers[i]();
    }
}

//解除綁定
function off(name) {
    delete events[name];
}

固然了,以上寫法有不少細節的地方沒有加入進來,好比上下文綁定對多種傳參方式的支持觸發事件時對事件處理器傳參的處理等等。redux

咱們知道,對於MVC來講,M(模型)的變化會反映在V(視圖)上,其實是視圖監聽了模型的變化,再根據模型去更新自身的狀態,這當中最重要的一個功能就是監聽(listen)。該功能也是由Events部分實現的,包括:listenTostopListening前端框架

listenToon相似,都是監聽一個事件,只不過listenTo是監聽其餘對象的對應事件,而on是監聽自身的對應事件。stopListening同理。好比:mvc

a.on('testevent', function(){
    alert('1');
});
a.trigger('testevent');

若是其餘對象但願監聽atestevent事件呢?則能夠經過listenTo來實現:app

b.listenTo(a, 'testevent', function() {
    alert('catch a\'s testevent');
})

其中第一個參數爲要監聽的對象,第二個參數爲事件名稱框架

當調用on方法的時候,會爲對象自身建立一個_event屬性;而調用listenTo方法時,會爲監聽對象建立_event屬性,同時爲了記錄監聽者,被監聽對象還會建立一個_listeners屬性:dom

a.on('testevent', handlers1);

a會變成:

{
    _events: {
        testevent: [handlers1]
    },
    on: function() {
        //...
    }
    ...
}

當有其餘對象監聽a時,如:

b.listenTo(a, 'testevent', handlers2);

a會變成:

{
    _events: {
        testevent: [handlers1, handlers2]
    },
    _listeners: b,
    on: function() {
        //...
    }
    ...
}

在事件機制的實現部分,除了核心邏輯以外,在對一些方法的使用上,也很考究。爲了綁定函數執行的上下文,咱們常常會使用applycall這些方法,而源碼中屢次提到apply的執行效率要低一些,所以,有這樣的實現:

// A difficult-to-believe, but optimized internal dispatch function for
// triggering events. Tries to keep the usual cases speedy (most internal
// Backbone events have 3 arguments).
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;
    }
};

有關爲何callapply的效率更高的解釋能夠參考這篇文章

模型(Model)

model用於維護數據,其中最關鍵的是對數據的更新部分,即set

// Trigger all relevant attribute changes.
if (!silent) {
    if (changes.length) this._pending = options;
    for (var i = 0; i < changes.length; i++) {
        this.trigger('change:' + changes[i], this, current[changes[i]], options);
    }
}

// You might be wondering why there's a `while` loop here. Changes can
// be recursively nested within `"change"` events.
if (changing) return this;
if (!silent) {
    while (this._pending) {
        options = this._pending;
        this._pending = false;
        this.trigger('change', this, options);
    }
}
this._pending = false;
this._changing = false;
return this;

每次set數據的時候,根據數據變化的部分,使用trigger方法觸發相應的事件;在set數據時,若是不但願觸發change事件,能夠設置silenttrue

這部分比較容易讓人產生疑惑的是while循環部分,這個while循環有什麼用呢?舉個例子:

new Model.on("change", function() {
    console.log('model change');
}).set({
    a: 1
});

以上代碼是最簡單的狀況,監聽change事件,當model變化時,打印出model change
在源碼中,當第一次進入while後,緊接着this._pending被置爲false,而事件觸發回調函數也不會更改this._pending的值,所以再次判斷時條件不成立,while內的代碼段只會執行一次。

可是實際狀況每每不是這麼簡單,如代碼註釋中所說,有可能會有嵌套的狀況,好比:

new Model.on("change", function() {
    this.set({
        b: 1
    })
}).set({
    a: 1
});

在這種狀況下,第一次trigger觸發change的回調函數中,又再次對model進行了更新操做,

this.set({
    b: 1
})

每次set時,會更新this._pendingtrue,這樣當set b後,就會再次進入while內,觸發change事件。而若是沒有使用while循環的話,對b屬性更新的操做就沒法觸發change事件,致使其監聽者到沒法根據最新的數據更新自身狀態。

視圖(View)

View部分的實現比較簡單,其中最主要的是events部分,一般在一個View中,都會綁定一些dom事件,好比:

{
    'click .preview-btn': 'preview',
    'click .save-btn': 'save'
}

主要有兩點須要說明:

  • backbone中是採用的事件委託的方式綁定事件,所以,一些不冒泡的事件,好比scroll,是沒法經過這樣的方式綁定的
  • 回調函數會保持正確的this指向。backbone內部進行了處理
delegateEvents: function(events) {
    events || (events = _.result(this, 'events'));
    if (!events) return this;
    this.undelegateEvents();
    for (var key in events) {
        var method = events[key];
        if (!_.isFunction(method)) method = this[method];
        if (!method) continue;
        var match = key.match(delegateEventSplitter);
        this.delegate(match[1], match[2], _.bind(method, this));
    }
    return this;
}

結語

以上部分介紹了backbone中最核心部分的實現機制。能夠看到其實現很是的簡單,可是對於小型項目來講,確實能夠幫咱們作一些對數據的維護和管理工做,提升開發效率。可是隨着業務逐漸複雜,會愈來愈發現,backbone所能作的實現有限,而對於數據維護部分也很是不方便,尤爲是須要是對多個模塊間的通訊和數據維護問題。後續我會結合在複雜業務中的使用談一談backbone的缺點,以及更優的框架能帶來的便利。

說句題外話,雖然去年因爲時間緣由選擇了backbone,這一年基本沒有在複雜業務場景中使用react技術棧,都是本身作個小demo練手。可是也正是由於有了使用backbone去寫複雜業務的經歷,在數據維護上和模塊間通訊上很是麻煩,以及backbone渲染dom時直接所有更新的會致使的頁面渲染性能問題,才更讓我感受react + redux的美好。知其然,還需知其因此然啊~ ~ 否則我以爲我可能會一直疑惑爲何要用一套這麼複雜的技術棧,異步請求這塊寫起來還那麼麻煩。這麼看,壞事也算是好事了吧~~

相關文章
相關標籤/搜索