自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

前端Backbone源碼解讀(一)

開發(fā) 架構
MVC在前端開始流行還是backbone的功勞。backbone的源碼相對于其他框架來說很短。所以雖然感覺用backbone寫應用很不容易,但是認真去讀backbone源碼還是可以加讀懂不少的。

MVC?

MVC是一種GUI軟件的一種架構模式。它的目的是將軟件的數(shù)據(jù)層(Model)和視圖(view)分開。Model連接數(shù)據(jù)庫,實現(xiàn)數(shù)據(jù)的交互。用戶不能直接和數(shù)據(jù)打交道,而是需要通過操作視圖,然后通過controller對事件作出響應,***才得以改變數(shù)據(jù)。***數(shù)據(jù)改變,通過觀察者模式更新view。(所以在這里需要用到設計模式中的觀察者模式)

Smalltalk-80 MVC

Smalltalk-80是早期的對MVC模式的一種實現(xiàn)。這種模式的目的是分離應用的內部邏輯和用戶的交互界面。在書中講述了這種模式有幾個特點:

  • 用戶操作界面(view和controller)和數(shù)據(jù)層(Model)是分離的。
  • 數(shù)據(jù)的呈現(xiàn)是由view和controller完成的。它們兩者沒有明顯的分界。(controller并非必須,可以用其他替代,因此就有了MVP和MVVM。)
  • controller的任務就是處理用戶操作view發(fā)出的事件。比如點擊,輸入等等。
  • model一旦發(fā)生改變就會通過觀察者模式更新view。

后端

我接觸過python的flask和node的express框架,都是以MVC的形式來組織的。V層用模板引擎呈現(xiàn)頁面,用戶對V層做操作,觸發(fā)訂閱好的事件,然后路由操作數(shù)據(jù)庫,***重新呈現(xiàn)頁面,達到更新的效果。個人感覺對后端來說,MVC的概念會更加直接和清晰。

前端backbone

廢話很多,下面直接進入正題了。MVC在前端開始流行(當然現(xiàn)在什么MVVM更火)還是backbone的功勞。backbone的源碼相對于其他框架來說很短(1.3.3版本的有2027行)。所以雖然感覺用backbone寫應用很不容易,但是認真去讀backbone源碼還是可以加讀懂不少的。我會分三篇文章去分析backbone的源碼。以下:

  • backbone的總結架構和Events
  • model & collection & view
  • sync & router & history

我看過很多人想寫backbone的源碼分析,寫得都很不錯,看了很有收獲,然而...大都都是些了一篇兩篇就停更了,悲傷的故事...希望我能夠堅持下來吧。

總體架構

終于開始啦!backbone里代碼結構和官方文檔里面的組織方式幾乎是一模一樣的,所以把官方文檔當成索引來讀也是很方便的~代碼的整體架構如下:

  1. (function(factory) { 
  2.     // 在這里是backbone模塊化的一個接口。支持AMD,CMD和全局變量模式。代碼很好理解。 
  3. })(function(root, factory, _, $) { 
  4.  
  5.     // 各種參數(shù)和函數(shù)的定義 
  6.  
  7.     Backbone.noConflict = function(){}; 
  8.  
  9.     var Events = Backbone.Events = {}; 
  10.     // 然后是各種Events方法的添加 
  11.     // Events在Backbone里面非常重要,Model,Collection和View都extend了它。(不知道怎么說才自然...)所以他們都可以發(fā)起訂閱事件,發(fā)起事件。當然,用戶也可以自己拿自己的對象拓展一下,那樣也可以訂閱發(fā)起事件了~ 
  12.  
  13.     var Model = Backbone.Model = function(){}; 
  14.     _.extend(Model.prototype, Events, { 
  15.         // 這里是各種對Model.prototype的拓展,定義各種方法 
  16.     }); 
  17.  
  18.     var Collection = Backbone.Collection = function(){}; 
  19.     _.extend(Collection.prototype, Events, { 
  20.         // 這里是各種對Collection.prototype的拓展,定義各種方法 
  21.     }); 
  22.  
  23.     var View = Backbone.View = function(){}; 
  24.     _.extend(View.prototype, Events, { 
  25.         // 這里是各種對View.prototype的拓展,定義各種方法 
  26.     }); 
  27.  
  28.     Backbone.sync = function(){}; 
  29.     Backbone.ajax = function(){}; 
  30.  
  31.     var Router = Backbone.Router = function(){}; 
  32.     _.extend(Router.prototype, Events, { 
  33.         // 這里是各種對Router.prototype的拓展,定義各種方法 
  34.     }); 
  35.  
  36.     var History = Backbone.History = function(){}; 
  37.     _.extend(History.prototype, Events, { 
  38.         // 這里是各種對History.prototype的拓展,定義各種方法 
  39.     }); 
  40.     // 用History定義實例 
  41.     Backbone.history = new History; 
  42.  
  43.     // 接下來是helper函數(shù)extend 
  44.     var extend = function(){}; 
  45.     Model.extend = Collection.extend = Router.extend = View.extend = History.extend = extend; 
  46.     // 其他的還有urlError,warpError函數(shù) 
  47.  
  48.     return Backbone; 
  49. });  

在這一小節(jié)我順便把除了model & collection & view & sync & router & history相關之外的都講了先吧

noConflict

防止沖突,如果自己本身全局就有Backbone,可以用noConflict解決沖突。不過,一般都不會有人起一個會沖突的名字吧...

  1. var previousBackbone = root.Backbone; 
  2. Backbone.noConflict = function() { 
  3.     root.Backbone = previousBackbone; 
  4.     return this; 
  5. };  

extend

這個函數(shù)返回了一個對象。這個對象的屬性,方法,構造函數(shù),原型都有了定義,很完整。

  1. var extend = function(protoProps, staticProps) { 
  2.     var parent = this; 
  3.     var child; 
  4.  
  5.     // 如果protoProps有構造函數(shù)就給child吧。 
  6.     if (protoProps && _.has(protoProps, 'constructor')) { 
  7.       child = protoProps.constructor; 
  8.     } else { 
  9.     // 如果沒有就用parent的。 
  10.       child = function(){ return parent.apply(this, arguments); }; 
  11.     } 
  12.  
  13.     // 把parent和staticProps的屬性方法給child吧。 
  14.     _.extend(child, parent, staticProps); 
  15.  
  16.     // 定義child的prototype。child是繼承自parent的。這里不直接調用構造函數(shù)。 
  17.     child.prototype = _.create(parent.prototype, protoProps); 
  18.     child.prototype.constructor = child; 
  19.  
  20.     child.__super__ = parent.prototype; 
  21.  
  22.     return child; 
  23. };  

這個函數(shù)理解起來并不困難,但在整個backbone里面很關鍵。因為不管是Model還是Collection還是Router等都需要Events的方法來做一些事件相關的操作。

  1. // 大家都需要extend這個方法。 
  2.  
  3. Model.extend = Collection.extend = Router.extend = View.extend = History.extend = extend;  

Events

backbone的Events和nodejs的EventEmmiter有很多類似的地方。其實本質上就是一個發(fā)布訂閱模式的算是比較常見的實現(xiàn)。

開始這段代碼我看了一個早上,不長,但是里面有一點繞。后來看到這篇文章之后(他講得很棒!選取的角度非常不錯!但是停!更!了!...悲傷...)就開始理解了。但由于版本不同,差別還是不少。

這里我打算從兩個方面來講解這個Events,一個是內部對象,一個是主要方法。事實上,在傳統(tǒng)的發(fā)布訂閱模式中,主要也是這兩個組成部分。由內部對象來管理所有的事件,由方法來做訂閱,發(fā)布,取消等的操作。具體來說,就是通過Events當中的this的各個屬性,來存儲,管理事件。而外部,則通過on,off,listenTo等方法來操作這些屬性。

具體的bakcbone代碼可以看這里。在文中不會大段大段貼代碼。不過強烈建議對照著看。

Events中的this

Events中的內部對象this起得作用是管理所有的事件,所有的監(jiān)聽和所有的被監(jiān)聽??梢試L試下把官方的todo范例的view里輸出一下console.log(this),其中的_listeningTo, _events, _listenId, _listeners都是Events帶來的內部函數(shù)的屬性。下面的關鍵方法,其實一定程度上都是操作這些內部屬性的方法。其中有一點需要注意,并不是每一個有Events方法的對象都會有著四個屬性。記住一點,只有需要的時候才會創(chuàng)建,不需要的時候是沒有的。設計模式在這里有很多涉及,可以去了解一下。下面講講這四個內部屬性。

  • _listeningTo: 當前對象所監(jiān)聽的對象。對象里面是一個或多個以被監(jiān)聽對象的_listenId為名字的對象。每一個對象結構如下:
    1.     count: 5, // 監(jiān)聽了幾個事件 
    2.     id: 13, // 監(jiān)聽方的id 
    3.     listeningTo: Object, // 自身相關的一些信息(很有趣,里面可以***點擊下去,因為引用了自身。不知道有什么用意...) 
    4.     obj: child, // 被監(jiān)聽的對象 
    5.     objId: "12" // 被監(jiān)聽對象id 
  • _listenId: 監(jiān)聽與被監(jiān)聽時候的標示
  • _listeners: 監(jiān)聽該對象的對象信息(有點繞,就是指“看著”它的對象)結構與_listeningTo類似。
  • _events: 一般是被監(jiān)聽對象或者說是用了on的對象才有的。一個name帶有幾個對象是一般常見的情況。

這里面有很多循環(huán)引用的地方,細細看才不會被看繞啊。

Events中的關鍵方法與函數(shù)

關鍵方法是寫代碼的時候用到的方法,算是一種接口,可以對內部對象的屬性做出改變。

在看backbone的時候,(其實不單只backbone,大部分寫得良好的,復用率高的代碼),總會覺得很繁瑣。但其實這才是良好代碼應該有的樣子:函數(shù)分工明確,各司其職,沒有重復代碼。很值得學習。雖然略微增加了閱讀的難度...要認真分析函數(shù)在哪里調用,數(shù)據(jù)的流向等等才能很好地理解。比如一個on函數(shù),里面調用了internalOn,internalOn函數(shù)傳入了一個onApi,調用了eventsApi,onApi在eventsApi里面調用,往_events里面添加了新的事件。這只是一個例子,其他的其實都類似。

eventsApi(輔助函數(shù))

這是一個有趣的函數(shù),它只是提供一個api接口,起到分流的作用。函數(shù)中根據(jù)不同的name的形式作出不同的調用調整。使得代碼得到很好的復用。傳入的參數(shù)及其作用是:

  • iteratee 實際真正要調用的函數(shù)
  • events 事件,有很多情況中傳入的是this._events
  • name 自己起的名字或者之前起的名字,代表了一個事件
  • callback 回調函數(shù),觸發(fā)事件時觸發(fā)
  • opts 參數(shù),在iteratee函數(shù)的內部有自己的作用

在進入它的函數(shù)的時候,會有一個判斷,把整一個函數(shù)內部分成三個部分,分別處理三種不同的情況。三種不同的情況分別是name是一個對象,一個有空格的字符串,一個普通字符串。根據(jù)三種不同的情況,對name進行處理,然后調用iteratee函數(shù)。

on

on方法的實質是把事件添加到this._events里面,非常直觀。但是由于函數(shù)調用感覺好像復雜了。在on里面調用了internalOn,internalOn把函數(shù)onApi傳給了eventsApi,eventsApi里面調用了onApi,然后就把事件的信息push進_events中。

onApi(輔助函數(shù))

這個函數(shù)很簡單,處理的事情就是往this._events里面push進相應的事件。一般是有添加進新函數(shù)的時候才會調用到這個函數(shù)。值的注意的是描述一個事件的時候往往還需要一些其他的參數(shù),這時候就需要options來提供了。

off

off和on其實類似,只是把上面的onApi換成了offApi函數(shù),其他都是大體一致的。要看offApi的具體實現(xiàn)可以看下面。

offApi(輔助函數(shù))

取消事件有幾種情況。當stopListening調用它的時候就不需要留下任何監(jiān)聽函數(shù),而用off的時候則還需要留下一些不應該刪除的函數(shù)。刪除分兩步,***步是刪除自己的,把監(jiān)聽該對象的listener刪除。再第二部就是把那一個listener的listeningTo刪除。其實這種刪除方式和后端數(shù)據(jù)庫的一些操作非常相似。刪除是兩個方面的。

listenTo

其實看上去繁瑣,這個函數(shù)的作用就是構建_listeningTo的一個過程。這個對象具體的形式在上面已經(jīng)講解過了。

stopListening

這個函數(shù)就是把對象所有監(jiān)聽的都清除掉。這個函數(shù)的內部原理也很簡單,就是把_listeningTo遍歷一遍),***調用off取消掉所有的被監(jiān)聽者listeners里面的相應的監(jiān)聽者。

once & listenToOnce

兩者內部很相近,都是調用eventsApi把要執(zhí)行的函數(shù)onceApi傳進去。差別在于once是***是調用on,而listenToOnce***調用listenTo。他們都是調用了一次就off掉的,原理在下面的onceMap介紹里面有講解。

onceMap(輔助函數(shù))

這個地方不好懂的地方是這個offer。offer這里是一個特殊的options。如果之前調用的once,offer就是off,如果之前調用的是listenToOnce就是stopListening。意思都是取消放棄監(jiān)聽。然后才調用回調函數(shù)。這樣做就達到“一次性”事件的要求。這里還保留了一個_callback函數(shù)的目的是什么呢?

  1. once._callback = callback; 

這篇文章里說了,在offApi里面有這么一行判斷

  1. callback !== handler.callback._callback 

根據(jù)這個判斷,就會讓一次性函數(shù)不會得以保留,這樣也就達到了用完一次就刪除的目的。這樣在調用offer的時候才得以刪除之。

trigger

trigger函數(shù)和之前的一樣,也是委托了eventsApi,把輔助函數(shù)傳進去了。具體可以看下面 triggerApi & triggerEvents有詳細介紹。

triggerApi & triggerEvents (輔助函數(shù))

這兩個trigger的輔助函數(shù)是這樣工作的。在trigger函數(shù)里面把triggerApi函數(shù)傳給了eventsApi調用,而triggerApi調用了triggerEvents。在trigger里面先是把參數(shù)取出來。后來參數(shù)會傳到triggerApi里面。然后會開始判斷是否有這個事件啊,還有這個事件是不是“all”事件啊,等等。然后再調用triggerEvents,在這個函數(shù)里面就是循環(huán)執(zhí)行回調函數(shù)。(原本代碼注釋寫的迷之優(yōu)化(difficult-to-believe)其實很好理解,所謂能夠枚舉就枚舉嘛,總是或多或少能優(yōu)化的。)

總結

寫了一整天....真的好累...怪不得那么多人會放棄....希望明天的自己能夠抖擻精神,堅持更新...而且寫得過于詳細也不是很好。Model& Collection & View這三個部分是很多人寫過的部分。大致簡略一點吧。

在backbone方面還算是小白,如果文章中有錯誤請輕噴,相互學習~

責任編輯:龐桂玉 來源: segmentfault
相關推薦

2021-11-06 19:52:20

源碼ReadThread

2012-09-10 10:41:27

IBMdw

2010-01-27 10:37:17

Android圖片瀏覽

2015-06-15 10:32:44

Java核心源碼解讀

2024-10-28 08:15:32

2010-01-28 15:54:19

Android單元測試

2024-09-06 09:37:45

WebApp類加載器Web 應用

2010-01-26 13:55:57

Android分享功能

2020-01-17 09:00:00

HashMapJava編程語言

2017-01-12 14:52:03

JVMFinalRefere源碼

2023-10-13 07:29:23

PixiJSRunner

2024-10-31 09:24:42

2021-08-27 00:21:19

JSJust源碼

2022-07-19 13:51:47

數(shù)據(jù)庫Hikari連接池

2010-01-06 18:59:41

.Net Framew

2023-10-27 13:59:30

Mybatis占位符

2010-01-27 13:52:15

Android多媒體框

2021-05-14 08:33:02

Flink策略源碼

2021-07-11 18:06:18

緩存過期淘汰

2012-02-09 16:09:17

JavaScript
點贊
收藏

51CTO技術棧公眾號