本文已同步在個人博客前端
在這個react和vue如日中天、jquery逐漸被你們拋棄的年代,我仍是想要來講一說backbone。vue
16年6月初,在沒有任何前端框架使用經驗、js水平也較通常的狀況下,被告知須要在幾個工做日內搭建完成一個後臺管理系統,沒有頁面設計稿、沒有組件庫,一切都是從零開始。當時面臨兩個選擇,backbone和react。雖然我很但願可以拿react來練手,可是考慮到學習成本和項目時間問題,leader仍是建議我使用backbone來完成,就這樣,一直用到差很少如今。雖然到項目後期業務場景愈來愈複雜,backbone的這套技術棧體現出愈來愈多的問題,可是對於小型項目來講,我仍是認爲backbone是個不錯的選擇,並且學習成本低,上手極快~
backbone是個很是輕量的mvc庫,本文將基於backbone的源碼談一談其實現的核心部分,以及其中一些巧妙的思想和代碼實現技巧。react
事件部分的核心邏輯其實比較簡單,簡化一下能夠用以下的僞代碼來表示: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部分實現的,包括:listenTo、stopListening等前端框架
listenTo和on相似,都是監聽一個事件,只不過listenTo是監聽其餘對象的對應事件,而on是監聽自身的對應事件。stopListening同理。好比:mvc
a.on('testevent', function(){ alert('1'); }); a.trigger('testevent');
若是其餘對象但願監聽a的testevent事件呢?則能夠經過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() { //... } ... }
在事件機制的實現部分,除了核心邏輯以外,在對一些方法的使用上,也很考究。爲了綁定函數執行的上下文,咱們常常會使用apply,call這些方法,而源碼中屢次提到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; } };
有關爲何call比apply的效率更高的解釋能夠參考這篇文章
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事件,能夠設置silent爲true。
這部分比較容易讓人產生疑惑的是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._pending爲true,這樣當set b後,就會再次進入while內,觸發change事件。而若是沒有使用while循環的話,對b屬性更新的操做就沒法觸發change事件,致使其監聽者到沒法根據最新的數據更新自身狀態。
View部分的實現比較簡單,其中最主要的是events部分,一般在一個View中,都會綁定一些dom事件,好比:
{ 'click .preview-btn': 'preview', 'click .save-btn': 'save' }
主要有兩點須要說明:
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的美好。知其然,還需知其因此然啊~ ~ 否則我以爲我可能會一直疑惑爲何要用一套這麼複雜的技術棧,異步請求這塊寫起來還那麼麻煩。這麼看,壞事也算是好事了吧~~