Backbone源碼解讀(二)

1. 開場

強烈建議一邊看着源碼一邊讀本文章,本文不貼大段代碼。源碼地址
在寫backbone應用的時候,說實話,大部分的時間都是在寫這三個模塊的內容。關於這三個模塊的分析網上隨隨便便就能找到一堆還不錯的文章。但我但願可以找到一條線索,能把各自模塊的內部機理整理清楚。就像前一篇文章中介紹的Events那樣。Events整個模塊其實就是經過一些外部的方法來修改內部對象的屬性,從而達到事件管理的目的。以一條線索來看待整個模塊,一切都清晰瞭然了。下面就開始了~html

(最近要開學,要準備回學校上課,亂七八糟的東西不少,因此文章可能也會拖一陣子啦...可是我仍是很是但願可以寫下來,半途而廢的感受真心很差...node

這一篇文章主要講backboneModel, CollectionView。這三個模塊有不少類似的地方。這篇文章不會把模塊的每個方法都介紹一遍,由於只要看源碼就知道,其實主要的方法只有幾個,而不少其餘的模塊實際上只是在調用這幾個核心的方法而已。jquery

2. Model & Collection & View

首先講一下三者的類似之處。這一節讓咱們來看看這三個模塊一個整體結構。
這三個模塊在結構上和Events不一樣。他們先經過如下方式來定義構造函數。(以View爲例)git

var View = Backbone.View = function(options) {
    // 構造函數的內容
};

構造函數的內部通常會作如下幾個操做:github

  • 各類給內部對象設置屬性。(各類this.a = bweb

  • 調用preinitializeajax

this.preinitialize.apply(this, arguments);
  • 調用initialize數據庫

this.initialize.apply(this, arguments);

各個模塊的方法和屬性是經過underscoreextend來得到的。注意在extend新加入的方法和屬性中,如下劃線開頭的變量是內部函數名。(其實理論上用戶也能夠調用這些方法,誰叫Javascript沒有內部變量呢...)這些內部方法是供本身模塊內部調用的。segmentfault

_.extend(View.prototype, Events, {
    // 這裏是各類對View.prototype的拓展,定義各類方法
});

還有一個比較大的共同點,就是slient參數。這個參數決定了是否要trigger一個事件,在源碼用佔了很大的篇幅對其進行分類討論。後端


3. Model

3.1 關鍵方法

有一些關鍵的方法一進入函數就會根據傳入的參數的形態進行變化。由於backbone一些方法支持兩個參數傳入或者一個數組傳入,這時候須要有個判斷。

3.2 set

set方法在model裏面是個很很差理解的東西,看了網上大多數解析感受都很模糊(並且遇到難理解的就用一些藉口矇混過去)。不得不說set裏面複雜精妙程度是每讀一遍驚歎一遍。
我想以變量的角度來說解多是一個比較好的角度。

  • changingthis._changing
    若是這個函數只是從頭執行到尾,那說實話,這兩個變量沒有任何意義。由於他們的值是肯定的。看函數開頭:

var changing   = this._changing;
this._changing = true;

在函數結尾:

this._changing = false;

這個changing將永遠永遠是false。我上網看到有人說多是webWorker,多線程相關的東西,但我直接在源碼console的時候卻發現,這個changing是會變的,並且我用得是todo範例。todo範例沒有任何相似webWorker的東西。這個假設猜想應該來講是不正確的。(不過這篇文章講得也很不錯啊)
因此這個changing到底有什麼用呢?答案就是遞歸函數set裏明明沒有遞歸啊?其實遞歸藏在了全部trigger的事件的回調函數裏面。源代碼下面的這一段:

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

這一個while裏的trigger使得函數發生遞歸,而後從新調用set。這樣的話,下一次changing就等於true了,這個變量的做用才能發揮。能夠看一下這個連接裏面的講解。

  • current變量是用來做爲引用改變attributes的,實際上是set能設置attributes的本質。

  • changes數組是用來存放改變了的key的,用於後期的事件觸發。

  • changed & _previousAttributes
    把這兩個放到一塊兒是由於他們的一個特殊的地方。我在todo的主函數的render裏面console,發現不論我作什麼操做,changed === {},_previousAttributes沒有發生改變。後來在查看官方文檔的時候,才瞭解previous的用法:

var bill = new Backbone.Model({
  name: "Bill Smith"
});

bill.on("change:name", function(model, name) {
  alert("Changed name from " + bill.previous("name") + " to " + name);
});

bill.set({name : "Bill Jones"});

set方法在被調用的時候,previous只有在回調函數裏纔能有用,也就是說,在回調函數外面想要用這個previous獲取前一個值是不可能的。它只能獲取到當前值。爲何呢?源碼作出瞭解釋。當用戶作出操做須要用到set方法的時候,其實set方法並非直接執行完就結束了。在這個方法裏面觸發了不少的事件,而previous只有在函數裏觸發了的事件的回調函數「裏面」才能返回正確的「前一個值」。changed也同理,由於不論中間如何變化,遞歸,到最後它會被設置爲{}

3.3 save

save方法的做用是把當前model的狀態保存到數據庫中,所以不可避免地要用到ajax。因爲backbone已經有了一個封裝好的方法sync用於觸發ajax,所以在save當中重點是設置參數。須要設置的有successerrormethod

  • success裏面會調用用戶傳入的回調函數並觸發sync事件表示已經同步了。

  • error用封裝好的wrapError函數,這個函數用得不少,用於處理錯誤。

  • method根據實際要用那種方法設置
    其中比較值得注意的是wait參數。這個參數會影響頁面更新的時機。若是waittrue的話,就會須要等到服務器端相應才更新頁面,不然就會當即更新。

3.4 destory

destory方法也是與ajax有密切聯繫的。主要也是設置ajax參數。它分了幾種不一樣的狀況並做出了相應的處理:

  • waitfalse,不用等待。發起delete請求,觸發內部函數destory

  • waittrue,發起ajax,等待服務器響應才觸發destory更新頁面。

  • 這是一個新的model,那就不須要發起請求了。

3.5 isValid

驗證函數,經過調用內部函數_validate,在經過這個函數調用validate函數。而後返回一個錯誤,若是沒有錯誤就返回true,不然觸發invalid,返回false


4. Collection

Collection相似一個數組,裏面存放着各類以model爲結構的對象。在Collection中也有這形式的判斷,若是傳入的參數是單個對象就會被轉換成數組。

4.1 set

這是Collection的一個很經常使用的方法,源碼中這一段很長,也有點繁瑣,可是沒有特別難以理解的地方。整個set的結構是:

  • 設置幾個數組(下面會詳細講)

  • 設置實際的models(修改this.models

  • trigger事件

主要來講就是有以下幾個關鍵點:

  • 若是不符合model形式,轉換之。

  • 設置相應的插入位置at

  • 設置set數組。set數組在裏面做用是爲給後面排序作準備。裏面存放的是新的Collectionmodels

  • 設置toAdd數組。這個數組是用於存儲新建的合法的model,而後須要調用內部函數_addReference設置索引於_byId數組,而且添加all事件(後面就能夠經過model直接trigger事件)。當slient不是true,後期能夠經過遍歷它來觸發add事件。

  • 設置toMerge數組。當這個model是本來已經存在的model的時候(cid匹配),就會修改,而後被push進這個數組中。

  • 設置toRemove數組。而後經過內部函數_removeModels刪除那些已經不在set裏面的models

  • 修改this.models,分兩種狀況,一種是直接整個替換掉,一種是後面再添加。

  • 若是silent不是true就要觸發事件。特別值得注意的一點是:這裏面的事件有兩種,一種事件是由Model發出的,一種事件是有Collection發出的。從Model發出的事件能夠很容易_addReference函數中發現

model.on('all', this._onModelEvent, this);

在這裏註冊了,調用的是_onModelEvent函數。而其餘沒有註冊的函數應該是給使用者註冊監聽用的。

4.2 sort

sort所依據的是用戶傳入的comparator參數,這個參數能夠是一個字符串也能夠是一個函數,若是是字符串就經過underscoresortBy方法,若是是個函數就直接傳入sort的第二個參數中。

4.3 fetch & create

fetchcreatebackbone與服務器端交互的一個接口。兩個方法內部處理其實都很好理解,就是設置ajax參數。最終本質上都是觸發sync。可是惟一不一樣的是fetch是經過自身的sync函數,但create是經過調用modelsave,而後觸發sync的。在

model.save(null, options);

跟着這個save函數裏面走,就會發現參數null傳入是有意義的。在save裏面的參數設置會很好地賦值並最後觸發sync,並且有一個頗有趣的點,就是這個createmodel傳上服務器,可是這個model是一個相對獨立的狀態,僅僅經過它的Collection屬性來維繫和Collection的關係。那就要求後端須要把這一個model添加到相應的Collection數據裏面去。

4.4 reference

Collection有一個值得關注的內部變量,那就是_byId,這個變量用cidid(因此model是一對一對出現的)來存儲Collection裏面的model,方便直接性的存取。在源碼中有不少操做目的就是刪除,增長,獲取這個內部變量的值。

4.5 CollectionIterator

這東西我以爲頗有意思...在官方文檔裏面沒有提到,可是因爲涉及到ES6的東西因此以爲有點眼前一亮的感受(哈哈哈),backbone在這裏用了Symbol.iterator,具體用法在這個連接裏有介紹,仍是挺清晰的。經過設置CollectionIteratorSymbol.iteratornext方法。它經過內部變量_kind來區分種類,_index來肯定對應的next的結果,這個對於寫迭代器仍是有點借鑑意義的~


5. View

在寫backbone應用的時候,View寫着寫着會愈來愈大...追根溯源,就是View的代碼不多...(大霧)。關於View,在寫相關代碼的時候有一些值是須要設置的(可選的)。下面的代碼就展現了可設置的參數,這些參數在View的方法中會用到(若是有的話)。

var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName', 'events'];

下面我會從兩個大的方面來解讀源碼,一個是element,一個是Events。整個View的源碼事實上就是這兩組東西。

5.1 Element

View字面意思是視圖,而在瀏覽器中,視圖就是html所呈現的頁面。每個View事實上就對應着html的一個元素(固然這個元素裏面能夠有不少不少元素)。這個元素默認標籤是div。與元素相關的代碼其實很簡單,首先要認清this.elthis.$el。前者是真正的節點,後者則是jquery對象的節點。後者因爲是jquery式的,所以就能夠作相關的jquery的操做。所以事件發起,刪除節點,設置屬性的操做都是jqueryapithis.$el或其子節點的操做。在進入構造函數的時候會調用一個叫_ensureElement的內部函數,在這個函數裏會根據用戶設置的參數去構建節點,最後展示到頁面之上。

5.2 Events

事件是View中很是重要的組成。這是用戶能夠操做數據的一個接口。在View裏面和數據相關的方法有delegateEventsdelegatesundelegateEventsundelegate。裏面經過使用者設置的events屬性來建立各類事件,操做各類事件。

{
    'mousedown .title':  'edit',
    'click .button':     'save',
    'click .open':       function(e) { ... }
}

events相關代碼很簡單,可是有一個很是很是巧妙的地方:就是做者用了jquery事件相關api命名空間。在delegate被調用的時候就給事件加上了一個特定的命名空間。

delegate: function(eventName, selector, listener) {
    this.$el.on(eventName + '.delegateEvents' + this.cid, selector, listener);
    return this;
}

所以在後續須要對總體的全部事件進行操做的時候就會方便不少不少。


6. 最後的話

此次源碼解析不能百分百保證是正確的,有一些混雜了本身的思考。由於不想像其餘大部分的源碼解析那樣,對於問題模糊處理。但我以爲仍是有意義的,由於每一個人讀的角度不同。兼聽則明,也但願讀者可以包容,但願深入理解backbone的讀者也請多讀幾篇文章,多讀幾遍源碼。下一篇文章要寫router & history,這一個模塊能夠單獨拆出來做爲SPA的一個入口,我的認爲這部分時backbonebackbone(骨架)。

但願可以堅持更下去吧,開學了,事情也開始多了起來...

本人仍是backbone小白,若是哪裏說錯了或者怎樣,請輕噴~相互學習~

下面是所有的文章:

相關文章
相關標籤/搜索