1. 開場
1.1 MVC?
MVC是一種GUI軟件的一種架構模式。它的目的是將軟件的數據層(Model)和視圖(view)分開。Model連接數據庫,實現數據的交互。用户不能直接和數據打交道,而是需要通過操作視圖,然後通過controller對事件作出響應,最後才得以改變數據。最後數據改變,通過觀察者模式更新view。(所以在這裏需要用到設計模式中的觀察者模式)
1.2 Smalltalk-80 MVC
Smalltalk-80是早期的對MVC模式的一種實現。這種模式的目的是分離應用的內部邏輯和用户的交互界面。在書中講述了這種模式有幾個特點:
-
用户操作界面(
view和controller)和數據層(Model)是分離的。 -
數據的呈現是由
view和controller完成的。它們兩者沒有明顯的分界。(controller並非必須,可以用其他替代,因此就有了MVP和MVVM。) -
controller的任務就是處理用户操作view發出的事件。比如點擊,輸入等等。 -
model一旦發生改變就會通過觀察者模式更新view。
1.3 後端
我接觸過python的flask和node的express框架,都是以MVC的形式來組織的。V層用模板引擎呈現頁面,用户對V層做操作,觸發訂閲好的事件,然後路由操作數據庫,最後重新呈現頁面,達到更新的效果。個人感覺對後端來説,MVC的概念會更加直接和清晰。
1.4 前端backbone
廢話很多,下面直接進入正題了。MVC在前端開始流行(當然現在什麼MVVM更火)還是backbone的功勞。backbone的源碼相對於其他框架來説很短(1.3.3版本的有2027行)。所以雖然感覺用backbone寫應用很不容易,但是認真去讀backbone源碼還是可以加讀懂不少的。我會分三篇文章去分析backbone的源碼。以下:
-
backbone的總結架構和Events -
model&collection&view -
sync&router&history
我看過很多人想寫backbone的源碼分析,寫得都很不錯,看了很有收穫,然而...大都都是些了一篇兩篇就停更了,悲傷的故事...希望我能夠堅持下來吧。
2. 總體架構
終於開始啦!backbone裏代碼結構和官方文檔裏面的組織方式幾乎是一模一樣的,所以把官方文檔當成索引來讀也是很方便的~代碼的整體架構如下:
(function(factory) {
// 在這裏是backbone模塊化的一個接口。支持AMD,CMD和全局變量模式。代碼很好理解。
})(function(root, factory, _, $) {
// 各種參數和函數的定義
Backbone.noConflict = function(){};
var Events = Backbone.Events = {};
// 然後是各種Events方法的添加
// Events在Backbone裏面非常重要,Model,Collection和View都extend了它。(不知道怎麼説才自然...)所以他們都可以發起訂閲事件,發起事件。當然,用户也可以自己拿自己的對象拓展一下,那樣也可以訂閲發起事件了~
var Model = Backbone.Model = function(){};
_.extend(Model.prototype, Events, {
// 這裏是各種對Model.prototype的拓展,定義各種方法
});
var Collection = Backbone.Collection = function(){};
_.extend(Collection.prototype, Events, {
// 這裏是各種對Collection.prototype的拓展,定義各種方法
});
var View = Backbone.View = function(){};
_.extend(View.prototype, Events, {
// 這裏是各種對View.prototype的拓展,定義各種方法
});
Backbone.sync = function(){};
Backbone.ajax = function(){};
var Router = Backbone.Router = function(){};
_.extend(Router.prototype, Events, {
// 這裏是各種對Router.prototype的拓展,定義各種方法
});
var History = Backbone.History = function(){};
_.extend(History.prototype, Events, {
// 這裏是各種對History.prototype的拓展,定義各種方法
});
// 用History定義實例
Backbone.history = new History;
// 接下來是helper函數extend
var extend = function(){};
Model.extend = Collection.extend = Router.extend = View.extend = History.extend = extend;
// 其他的還有urlError,warpError函數
return Backbone;
});
在這一小節我順便把除了model & collection & view & sync & router & history相關之外的都講了先吧
2.1 noConflict
防止衝突,如果自己本身全局就有Backbone,可以用noConflict解決衝突。不過,一般都不會有人起一個會衝突的名字吧...
var previousBackbone = root.Backbone;
Backbone.noConflict = function() {
root.Backbone = previousBackbone;
return this;
};
2.2 extend
這個函數返回了一個對象。這個對象的屬性,方法,構造函數,原型都有了定義,很完整。
var extend = function(protoProps, staticProps) {
var parent = this;
var child;
// 如果protoProps有構造函數就給child吧。
if (protoProps && _.has(protoProps, 'constructor')) {
child = protoProps.constructor;
} else {
// 如果沒有就用parent的。
child = function(){ return parent.apply(this, arguments); };
}
// 把parent和staticProps的屬性方法給child吧。
_.extend(child, parent, staticProps);
// 定義child的prototype。child是繼承自parent的。這裏不直接調用構造函數。
child.prototype = _.create(parent.prototype, protoProps);
child.prototype.constructor = child;
child.__super__ = parent.prototype;
return child;
};
這個函數理解起來並不困難,但在整個backbone裏面很關鍵。因為不管是Model還是Collection還是Router等都需要Events的方法來做一些事件相關的操作。
// 大家都需要extend這個方法。
Model.extend = Collection.extend = Router.extend = View.extend = History.extend = extend;
3. Events
backbone的Events和nodejs的EventEmmiter有很多類似的地方。其實本質上就是一個發佈訂閲模式的算是比較常見的實現。
開始這段代碼我看了一個早上,不長,但是裏面有一點繞。後來看到這篇文章之後(他講得很棒!選取的角度非常不錯!但是停!更!了!...悲傷...)就開始理解了。但由於版本不同,差別還是不少。
這裏我打算從兩個方面來講解這個Events,一個是內部對象,一個是主要方法。事實上,在傳統的發佈訂閲模式中,主要也是這兩個組成部分。由內部對象來管理所有的事件,由方法來做訂閲,發佈,取消等的操作。具體來説,就是通過Events當中的this的各個屬性,來存儲,管理事件。而外部,則通過on,off,listenTo等方法來操作這些屬性。
具體的bakcbone代碼可以看這裏。在文中不會大段大段貼代碼。不過強烈建議對照着看。
3.1 Events中的this
Events中的內部對象this起得作用是管理所有的事件,所有的監聽和所有的被監聽。可以嘗試下把官方的todo範例的view裏輸出一下console.log(this),其中的_listeningTo, _events, _listenId, _listeners都是Events帶來的內部函數的屬性。下面的關鍵方法,其實一定程度上都是操作這些內部屬性的方法。其中有一點需要注意,並不是每一個有Events方法的對象都會有着四個屬性。記住一點,只有需要的時候才會創建,不需要的時候是沒有的。設計模式在這裏有很多涉及,可以去了解一下。下面講講這四個內部屬性。
-
_listeningTo: 當前對象所監聽的對象。對象裏面是一個或多個以被監聽對象的_listenId為名字的對象。每一個對象結構如下:{ count: 5, // 監聽了幾個事件 id: 13, // 監聽方的id listeningTo: Object, // 自身相關的一些信息(很有趣,裏面可以無限點擊下去,因為引用了自身。不知道有什麼用意...) obj: child, // 被監聽的對象 objId: "12" // 被監聽對象id } -
_listenId: 監聽與被監聽時候的標示 -
_listeners: 監聽該對象的對象信息(有點繞,就是指“看着”它的對象)結構與_listeningTo類似。 -
_events: 一般是被監聽對象或者説是用了on的對象才有的。一個name帶有幾個對象是一般常見的情況。
這裏面有很多循環引用的地方,細細看才不會被看繞啊。
3.2 Events中的關鍵方法與函數
關鍵方法是寫代碼的時候用到的方法,算是一種接口,可以對內部對象的屬性做出改變。
在看backbone的時候,(其實不單隻backbone,大部分寫得良好的,複用率高的代碼),總會覺得很繁瑣。但其實這才是良好代碼應該有的樣子:函數分工明確,各司其職,沒有重複代碼。很值得學習。雖然略微增加了閲讀的難度...要認真分析函數在哪裏調用,數據的流向等等才能很好地理解。比如一個on函數,裏面調用了internalOn,internalOn函數傳入了一個onApi,調用了eventsApi,onApi在eventsApi裏面調用,往_events裏面添加了新的事件。這只是一個例子,其他的其實都類似。
3.2.1 eventsApi(輔助函數)
這是一個有趣的函數,它只是提供一個api接口,起到分流的作用。函數中根據不同的name的形式作出不同的調用調整。使得代碼得到很好的複用。傳入的參數及其作用是:
-
iteratee實際真正要調用的函數 -
events事件,有很多情況中傳入的是this._events -
name自己起的名字或者之前起的名字,代表了一個事件 -
callback回調函數,觸發事件時觸發 -
opts參數,在iteratee函數的內部有自己的作用
在進入它的函數的時候,會有一個判斷,把整一個函數內部分成三個部分,分別處理三種不同的情況。三種不同的情況分別是name是一個對象,一個有空格的字符串,一個普通字符串。根據三種不同的情況,對name進行處理,然後調用iteratee函數。
3.2.2 on
on方法的實質是把事件添加到this._events裏面,非常直觀。但是由於函數調用感覺好像複雜了。在on裏面調用了internalOn,internalOn把函數onApi傳給了eventsApi,eventsApi裏面調用了onApi,然後就把事件的信息push進_events中。
3.2.3 onApi(輔助函數)
這個函數很簡單,處理的事情就是往this._events裏面push進相應的事件。一般是有添加進新函數的時候才會調用到這個函數。值的注意的是描述一個事件的時候往往還需要一些其他的參數,這時候就需要options來提供了。
3.2.4 off
off和on其實類似,只是把上面的onApi換成了offApi函數,其他都是大體一致的。要看offApi的具體實現可以看下面。
3.2.5 offApi(輔助函數)
取消事件有幾種情況。當stopListening調用它的時候就不需要留下任何監聽函數,而用off的時候則還需要留下一些不應該刪除的函數。刪除分兩步,第一步是刪除自己的,把監聽該對象的listener刪除。再第二部就是把那一個listener的listeningTo刪除。其實這種刪除方式和後端數據庫的一些操作非常相似。刪除是兩個方面的。
3.2.6 listenTo
其實看上去繁瑣,這個函數的作用就是構建_listeningTo的一個過程。這個對象具體的形式在上面已經講解過了。
3.2.7 stopListening
這個函數就是把對象所有監聽的都清除掉。這個函數的內部原理也很簡單,就是把_listeningTo遍歷一遍),最後調用off取消掉所有的被監聽者listeners裏面的相應的監聽者。
3.2.8 once & listenToOnce
兩者內部很相近,都是調用eventsApi把要執行的函數onceApi傳進去。差別在於once是最後是調用on,而listenToOnce最後調用listenTo。他們都是調用了一次就off掉的,原理在下面的onceMap介紹裏面有講解。
3.2.9 onceMap(輔助函數)
這個地方不好懂的地方是這個offer。offer這裏是一個特殊的options。如果之前調用的once,offer就是off,如果之前調用的是listenToOnce就是stopListening。意思都是取消放棄監聽。然後才調用回調函數。這樣做就達到“一次性”事件的要求。這裏還保留了一個_callback函數的目的是什麼呢?
once._callback = callback;
這篇文章裏説了,在offApi裏面有這麼一行判斷
callback !== handler.callback._callback
根據這個判斷,就會讓一次性函數不會得以保留,這樣也就達到了用完一次就刪除的目的。這樣在調用offer的時候才得以刪除之。
3.2.10 trigger
trigger函數和之前的一樣,也是委託了eventsApi,把輔助函數傳進去了。具體可以看下面 triggerApi & triggerEvents有詳細介紹。
3.2.11 triggerApi & triggerEvents (輔助函數)
這兩個trigger的輔助函數是這樣工作的。在trigger函數裏面把triggerApi函數傳給了eventsApi調用,而triggerApi調用了triggerEvents。在trigger裏面先是把參數取出來。後來參數會傳到triggerApi裏面。然後會開始判斷是否有這個事件啊,還有這個事件是不是“all”事件啊,等等。然後再調用triggerEvents,在這個函數裏面就是循環執行回調函數。(原本代碼註釋寫的迷之優化(difficult-to-believe)其實很好理解,所謂能夠枚舉就枚舉嘛,總是或多或少能優化的。)
4. 總結
寫了一整天....真的好累...怪不得那麼多人會放棄....希望明天的自己能夠抖擻精神,堅持更新...而且寫得過於詳細也不是很好。Model & Collection & View這三個部分是很多人寫過的部分。大致簡略一點吧。
在backbone方面還算是小白,如果文章中有錯誤請輕噴,相互學習~
下面是全部的文章:
-
基於 Backbone + node 的個人簡歷生成器(個人學習總結)
-
Backbone源碼解讀(一)
-
Backbone源碼解讀(二)
-
Backbone源碼解讀(三)