MVC
是一種GUI軟件的一種架構模式。它的目的是將軟件的數據層(Model
)和視圖(view
)分開。Model
鏈接數據庫,實現數據的交互。用戶不能直接和數據打交道,而是須要經過操做視圖,而後經過controller
對事件做出響應,最後才得以改變數據。最後數據改變,經過觀察者模式更新view
。(因此在這裏須要用到設計模式中的觀察者模式)前端
Smalltalk-80
是早期的對MVC
模式的一種實現。這種模式的目的是分離應用的內部邏輯和用戶的交互界面。在書中講述了這種模式有幾個特色:node
用戶操做界面(view
和controller
)和數據層(Model
)是分離的。python
數據的呈現是由view
和controller
完成的。它們二者沒有明顯的分界。(controller
並不是必須,能夠用其餘替代,所以就有了MVP
和MVVM
。)git
controller
的任務就是處理用戶操做view
發出的事件。好比點擊,輸入等等。github
model
一旦發生改變就會經過觀察者模式更新view
。ajax
我接觸過python
的flask
和node
的express
框架,都是以MVC
的形式來組織的。V層
用模板引擎呈現頁面,用戶對V層
作操做,觸發訂閱好的事件,而後路由操做數據庫,最後從新呈現頁面,達到更新的效果。我的感受對後端來講,MVC
的概念會更加直接和清晰。數據庫
廢話不少,下面直接進入正題了。MVC
在前端開始流行(固然如今什麼MVVM
更火)仍是backbone
的功勞。backbone
的源碼相對於其餘框架來講很短(1.3.3
版本的有2027行)。因此雖然感受用backbone
寫應用很不容易,可是認真去讀backbone
源碼仍是能夠加讀懂很多的。我會分三篇文章去分析backbone
的源碼。如下:express
backbone
的總結架構和Events
flask
model
& collection
& view
segmentfault
sync
& router
& history
我看過不少人想寫backbone
的源碼分析,寫得都很不錯,看了頗有收穫,然而...大都都是些了一篇兩篇就停更了,悲傷的故事...但願我可以堅持下來吧。
終於開始啦!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
相關以外的都講了先吧
防止衝突,若是本身自己全局就有Backbone
,能夠用noConflict
解決衝突。不過,通常都不會有人起一個會衝突的名字吧...
var previousBackbone = root.Backbone; Backbone.noConflict = function() { root.Backbone = previousBackbone; return this; };
這個函數返回了一個對象。這個對象的屬性,方法,構造函數,原型都有了定義,很完整。
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;
backbone
的Events
和nodejs
的EventEmmiter
有不少相似的地方。其實本質上就是一個發佈訂閱模式的算是比較常見的實現。
開始這段代碼我看了一個早上,不長,可是裏面有一點繞。後來看到這篇文章以後(他講得很棒!選取的角度很是不錯!可是停!更!了!...悲傷...)就開始理解了。但因爲版本不一樣,差異仍是很多。
這裏我打算從兩個方面來說解這個Events
,一個是內部對象,一個是主要方法。事實上,在傳統的發佈訂閱模式中,主要也是這兩個組成部分。由內部對象來管理全部的事件,由方法來作訂閱,發佈,取消等的操做。具體來講,就是經過Events
當中的this
的各個屬性,來存儲,管理事件。而外部,則經過on
,off
,listenTo
等方法來操做這些屬性。
具體的bakcbone
代碼能夠看這裏。在文中不會大段大段貼代碼。不過強烈建議對照着看。
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
帶有幾個對象是通常常見的狀況。
這裏面有不少循環引用的地方,細細看纔不會被看繞啊。
關鍵方法是寫代碼的時候用到的方法,算是一種接口,能夠對內部對象的屬性作出改變。
在看backbone
的時候,(其實不單隻backbone
,大部分寫得良好的,複用率高的代碼),總會以爲很繁瑣。但其實這纔是良好代碼應該有的樣子:函數分工明確,各司其職,沒有重複代碼。很值得學習。雖然略微增長了閱讀的難度...要認真分析函數在哪裏調用,數據的流向等等才能很好地理解。好比一個on
函數,裏面調用了internalOn
,internalOn
函數傳入了一個onApi
,調用了eventsApi
,onApi
在eventsApi
裏面調用,往_events
裏面添加了新的事件。這只是一個例子,其餘的其實都相似。
這是一個有趣的函數,它只是提供一個api
接口,起到分流的做用。函數中根據不一樣的name的形式做出不一樣的調用調整。使得代碼獲得很好的複用。傳入的參數及其做用是:
iteratee
實際真正要調用的函數
events
事件,有不少狀況中傳入的是this._events
name
本身起的名字或者以前起的名字,表明了一個事件
callback
回調函數,觸發事件時觸發
opts
參數,在iteratee
函數的內部有本身的做用
在進入它的函數的時候,會有一個判斷,把整一個函數內部分紅三個部分,分別處理三種不一樣的狀況。三種不一樣的狀況分別是name
是一個對象,一個有空格的字符串,一個普通字符串。根據三種不一樣的狀況,對name
進行處理,而後調用iteratee
函數。
on
方法的實質是把事件添加到this._events
裏面,很是直觀。可是因爲函數調用感受好像複雜了。在on
裏面調用了internalOn
,internalOn
把函數onApi
傳給了eventsApi
,eventsApi
裏面調用了onApi
,而後就把事件的信息push
進_events
中。
這個函數很簡單,處理的事情就是往this._events
裏面push
進相應的事件。通常是有添加進新函數的時候纔會調用到這個函數。值的注意的是描述一個事件的時候每每還須要一些其餘的參數,這時候就須要options
來提供了。
off
和on
其實相似,只是把上面的onApi
換成了offApi
函數,其餘都是大致一致的。要看offApi
的具體實現能夠看下面。
取消事件有幾種狀況。當stopListening
調用它的時候就不須要留下任何監聽函數,而用off
的時候則還須要留下一些不該該刪除的函數。刪除分兩步,第一步是刪除本身的,把監聽該對象的listener
刪除。再第二部就是把那一個listener
的listeningTo
刪除。其實這種刪除方式和後端數據庫的一些操做很是類似。刪除是兩個方面的。
其實看上去繁瑣,這個函數的做用就是構建_listeningTo
的一個過程。這個對象具體的形式在上面已經講解過了。
這個函數就是把對象全部監聽的都清除掉。這個函數的內部原理也很簡單,就是把_listeningTo
遍歷一遍),最後調用off取消掉全部的被監聽者listeners
裏面的相應的監聽者。
二者內部很相近,都是調用eventsApi
把要執行的函數onceApi
傳進去。差異在於once
是最後是調用on
,而listenToOnce
最後調用listenTo
。他們都是調用了一次就off
掉的,原理在下面的onceMap
介紹裏面有講解。
這個地方很差懂的地方是這個offer
。offer
這裏是一個特殊的options
。若是以前調用的once
,offer
就是off
,若是以前調用的是listenToOnce
就是stopListening
。意思都是取消放棄監聽。而後才調用回調函數。這樣作就達到「一次性」事件的要求。這裏還保留了一個_callback
函數的目的是什麼呢?
once._callback = callback;
這篇文章裏說了,在offApi
裏面有這麼一行判斷
callback !== handler.callback._callback
根據這個判斷,就會讓一次性函數不會得以保留,這樣也就達到了用完一次就刪除的目的。這樣在調用offer
的時候才得以刪除之。
trigger
函數和以前的同樣,也是委託了eventsApi
,把輔助函數傳進去了。具體能夠看下面 triggerApi
& triggerEvents
有詳細介紹。
這兩個trigger
的輔助函數是這樣工做的。在trigger
函數裏面把triggerApi
函數傳給了eventsApi
調用,而triggerApi
調用了triggerEvents
。在trigger
裏面先是把參數取出來。後來參數會傳到triggerApi
裏面。而後會開始判斷是否有這個事件啊,還有這個事件是否是「all」
事件啊,等等。而後再調用triggerEvents
,在這個函數裏面就是循環執行回調函數。(本來代碼註釋寫的迷之優化(difficult-to-believe
)其實很好理解,所謂可以枚舉就枚舉嘛,老是或多或少能優化的。)
寫了一成天....真的好累...怪不得那麼多人會放棄....但願明天的本身可以抖擻精神,堅持更新...並且寫得過於詳細也不是很好。Model
& Collection
& View
這三個部分是不少人寫過的部分。大體簡略一點吧。
在backbone方面還算是小白,若是文章中有錯誤請輕噴,相互學習~
下面是所有的文章: