強烈建議一邊看着源碼一邊讀本文章,本文不貼大段代碼。源碼地址。
在寫backbone
應用的時候,說實話,大部分的時間都是在寫這三個模塊的內容。關於這三個模塊的分析網上隨隨便便就能找到一堆還不錯的文章。但我但願可以找到一條線索,能把各自模塊的內部機理整理清楚。就像前一篇文章中介紹的Events
那樣。Events
整個模塊其實就是經過一些外部的方法來修改內部對象的屬性,從而達到事件管理的目的。以一條線索來看待整個模塊,一切都清晰瞭然了。下面就開始了~html
(最近要開學,要準備回學校上課,亂七八糟的東西不少,因此文章可能也會拖一陣子啦...可是我仍是很是但願可以寫下來,半途而廢的感受真心很差...node
這一篇文章主要講backbone
的Model
, Collection
和View
。這三個模塊有不少類似的地方。這篇文章不會把模塊的每個方法都介紹一遍,由於只要看源碼就知道,其實主要的方法只有幾個,而不少其餘的模塊實際上只是在調用這幾個核心的方法而已。jquery
首先講一下三者的類似之處。這一節讓咱們來看看這三個模塊一個整體結構。
這三個模塊在結構上和Events
不一樣。他們先經過如下方式來定義構造函數。(以View
爲例)git
var View = Backbone.View = function(options) { // 構造函數的內容 };
構造函數的內部通常會作如下幾個操做:github
各類給內部對象設置屬性。(各類this.a = b
)web
調用preinitialize
ajax
this.preinitialize.apply(this, arguments);
調用initialize
數據庫
this.initialize.apply(this, arguments);
各個模塊的方法和屬性是經過underscore
的extend
來得到的。注意在extend
新加入的方法和屬性中,如下劃線開頭的變量是內部函數名。(其實理論上用戶也能夠調用這些方法,誰叫Javascript
沒有內部變量呢...)這些內部方法是供本身模塊內部調用的。segmentfault
_.extend(View.prototype, Events, { // 這裏是各類對View.prototype的拓展,定義各類方法 });
還有一個比較大的共同點,就是slient
參數。這個參數決定了是否要trigger
一個事件,在源碼用佔了很大的篇幅對其進行分類討論。後端
有一些關鍵的方法一進入函數就會根據傳入的參數的形態進行變化。由於backbone一些方法支持兩個參數傳入或者一個數組傳入,這時候須要有個判斷。
set
方法在model
裏面是個很很差理解的東西,看了網上大多數解析感受都很模糊(並且遇到難理解的就用一些藉口矇混過去)。不得不說set
裏面複雜精妙程度是每讀一遍驚歎一遍。
我想以變量的角度來說解多是一個比較好的角度。
changing
和this._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
也同理,由於不論中間如何變化,遞歸,到最後它會被設置爲{}
。
save
方法的做用是把當前model
的狀態保存到數據庫中,所以不可避免地要用到ajax
。因爲backbon
e已經有了一個封裝好的方法sync
用於觸發ajax
,所以在save
當中重點是設置參數。須要設置的有success
,error
,method
。
在success
裏面會調用用戶傳入的回調函數並觸發sync
事件表示已經同步了。
error
用封裝好的wrapError
函數,這個函數用得不少,用於處理錯誤。
method
根據實際要用那種方法設置
其中比較值得注意的是wait
參數。這個參數會影響頁面更新的時機。若是wait
是true
的話,就會須要等到服務器端相應才更新頁面,不然就會當即更新。
destory
方法也是與ajax
有密切聯繫的。主要也是設置ajax
參數。它分了幾種不一樣的狀況並做出了相應的處理:
wait
是false
,不用等待。發起delete
請求,觸發內部函數destory
。
wait
是true
,發起ajax
,等待服務器響應才觸發destory
更新頁面。
這是一個新的model
,那就不須要發起請求了。
驗證函數,經過調用內部函數_validate
,在經過這個函數調用validate
函數。而後返回一個錯誤,若是沒有錯誤就返回true
,不然觸發invalid
,返回false
。
Collection
相似一個數組,裏面存放着各類以model
爲結構的對象。在Collection
中也有這形式的判斷,若是傳入的參數是單個對象就會被轉換成數組。
這是Collection
的一個很經常使用的方法,源碼中這一段很長,也有點繁瑣,可是沒有特別難以理解的地方。整個set
的結構是:
設置幾個數組(下面會詳細講)
設置實際的models
(修改this.models
)
trigger
事件
主要來講就是有以下幾個關鍵點:
若是不符合model
形式,轉換之。
設置相應的插入位置at
。
設置set
數組。set
數組在裏面做用是爲給後面排序作準備。裏面存放的是新的Collection
的models
。
設置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
函數。而其餘沒有註冊的函數應該是給使用者註冊監聽用的。
sort
所依據的是用戶傳入的comparator
參數,這個參數能夠是一個字符串也能夠是一個函數,若是是字符串就經過underscore
的sortBy
方法,若是是個函數就直接傳入sort
的第二個參數中。
fetch
和create
是backbone
與服務器端交互的一個接口。兩個方法內部處理其實都很好理解,就是設置ajax
參數。最終本質上都是觸發sync
。可是惟一不一樣的是fetch
是經過自身的sync
函數,但create
是經過調用model
的save
,而後觸發sync
的。在
model.save(null, options);
跟着這個save
函數裏面走,就會發現參數null傳入是有意義的。在save
裏面的參數設置會很好地賦值並最後觸發sync
,並且有一個頗有趣的點,就是這個create
把model
傳上服務器,可是這個model
是一個相對獨立的狀態,僅僅經過它的Collection
屬性來維繫和Collection
的關係。那就要求後端須要把這一個model
添加到相應的Collection
數據裏面去。
在Collection
有一個值得關注的內部變量,那就是_byId
,這個變量用cid
和id
(因此model
是一對一對出現的)來存儲Collection
裏面的model
,方便直接性的存取。在源碼中有不少操做目的就是刪除,增長,獲取這個內部變量的值。
這東西我以爲頗有意思...在官方文檔裏面沒有提到,可是因爲涉及到ES6
的東西因此以爲有點眼前一亮的感受(哈哈哈),backbone
在這裏用了Symbol.iterator
,具體用法在這個連接裏有介紹,仍是挺清晰的。經過設置CollectionIterator
的Symbol.iterator
和next
方法。它經過內部變量_kind
來區分種類,_index
來肯定對應的next
的結果,這個對於寫迭代器仍是有點借鑑意義的~
在寫backbone
應用的時候,View
寫着寫着會愈來愈大...追根溯源,就是View
的代碼不多...(大霧)。關於View
,在寫相關代碼的時候有一些值是須要設置的(可選的)。下面的代碼就展現了可設置的參數,這些參數在View的方法中會用到(若是有的話)。
var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName', 'events'];
下面我會從兩個大的方面來解讀源碼,一個是element
,一個是Events
。整個View
的源碼事實上就是這兩組東西。
View
字面意思是視圖,而在瀏覽器中,視圖就是html
所呈現的頁面。每個View
事實上就對應着html
的一個元素(固然這個元素裏面能夠有不少不少元素)。這個元素默認標籤是div
。與元素相關的代碼其實很簡單,首先要認清this.el
和this.$el
。前者是真正的節點,後者則是jquery
對象的節點。後者因爲是jquery
式的,所以就能夠作相關的jquery
的操做。所以事件發起,刪除節點,設置屬性的操做都是jquery
的api
對this.$el
或其子節點的操做。在進入構造函數的時候會調用一個叫_ensureElement
的內部函數,在這個函數裏會根據用戶設置的參數去構建節點,最後展示到頁面之上。
事件是View
中很是重要的組成。這是用戶能夠操做數據的一個接口。在View裏面和數據相關的方法有delegateEvents
,delegates
,undelegateEvents
,undelegate
。裏面經過使用者設置的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; }
所以在後續須要對總體的全部事件進行操做的時候就會方便不少不少。
此次源碼解析不能百分百保證是正確的,有一些混雜了本身的思考。由於不想像其餘大部分的源碼解析那樣,對於問題模糊處理。但我以爲仍是有意義的,由於每一個人讀的角度不同。兼聽則明,也但願讀者可以包容,但願深入理解backbone
的讀者也請多讀幾篇文章,多讀幾遍源碼。下一篇文章要寫router & history
,這一個模塊能夠單獨拆出來做爲SPA
的一個入口,我的認爲這部分時backbone
的backbone
(骨架)。
但願可以堅持更下去吧,開學了,事情也開始多了起來...
本人仍是backbone
小白,若是哪裏說錯了或者怎樣,請輕噴~相互學習~
下面是所有的文章: