【 js 基礎 】【 源碼學習 】源碼設計 (更新了backbone分析)
學習源碼,除了學習對一些方法的更加聰明的代碼實現,同時也要學習源碼的設計,把握整體的架構。(推薦對源碼有一定熟悉了之後,再看這篇文章)
目錄結構:
第一部分:zepto 設計分析第二部分:underscore 設計分析
第三部分:backbone 設計分析
第一部分: zepto 設計分析
zepto 是一個輕量級的 Javascript 庫。相對於 jquery 來說在 size 上更加小,主要是定位於移動設備。它是非常好的學習源碼的入門級 javascript 庫。這裡重點說一下,這個庫的設計,而對於詳細的源碼學習大家可以 star 我的 github 源碼學習項目(JiayiLi/source-code-study) 進行關注。讓我們先看看把所有代碼刪除只剩下這幾行的 zepto :
var Zepto = (function() {ntreturn $n})() nwindow.Zepto = Zepto nwindow.$ === undefined && (window.$ = Zepto)n
一個匿名自執行函數返回$傳遞給了Zepto。然後把Zepto掛到window上,使其成為全局 window 的一個屬性,同時,如果window.$ 符號沒有被佔用,那麼$會被賦值為Zepto,故可以全局範圍內使用$。
然後 咱們再來看看 賦值給 zepto 的匿名自執行函數 的核心代碼 具體幹了什麼:
var Zepto = (function() {nt//zepto和$是Zepto中的兩個命名空間,用來掛載靜態函數ntvar $,zepto = {};nntfunction Z(dom, selector) {nttvar i, len = dom ? dom.length: 0nttfor (i = 0; i < len; i++) this[i] = dom[i] this.length = len this.selector = selector || nt}nntzepto.Z = function(dom, selector) {nttreturn new Z(dom, selector)nt}ntzepto.init = function(selector, context) {....nttreturn zepto.Z(dom, selector)nt}nnt//$()的返回值能夠調用$.fn中的方法nt$ = function(selector, context) {nttreturn zepto.init(selector, context);nt}nnt$.fn = {ntt// 裡面有若干個工具函數nt};nntzepto.Z.prototype = Z.prototype = $.fnnt$.zepto = zeptonntreturn $ //返回$,賦值給Zepton})()nnwindow.Zepto = Zepton//當$未被佔用時就把Zepto賦值給$nwindow.$ === undefined && (window.$ = Zepto)n
首先定義了 兩個 變數 zepto 和 $ ,還有一個 構造函數 Z 。
對於變數 zepto ,給其定義了兩個方法 Z 和 init。init 方法中調用了 zepto 的 Z 方法,而在 zepto 的 Z 方法中 則 實例化了 構造函數 Z。
對於 變數 $ ,則是個函數,內部調用了 zepto 的 init 方法 ,也就是最後返回了 構造函數 Z 的 新實例 。同時也給 $ 上定義了一個屬性 fn,fn 是一個 對象,這個對象裡面實現了多個工具函數,比如 concat、slice、each、filter 等。
然後將 剛才定義的 變數 zepto 中的 Z 方法的 原型 即構造函數的原型 指向了 $.fn ,這樣 調用 $ 函數所返回的 Z 實例 ,就繼承了 $.fn 中的方法。
最後通過 $.zepto = zepto 將內部API導出。即如果有需要可以調用 zepto 上的方法。
Z 實例 實際上一個 對象數組,即可以模擬數組操作的對象。
咱們再用個圖來梳理一下思路:

學習並感謝:
開始 - [ zepto對象思想與源碼分析 ] - 看雲
zepto源碼分析-代碼結構 - 劉振的技術成長之路 - SegmentFault
第二部分 underscore 設計分析
underscore 是一個Javascript 實用庫。是函數式編程的典型代表。它是非常好的學習源碼的入門級 javascript 庫,尤其是學習函數式編程的好材料。這裡重點說一下,這個庫的設計,而對於詳細的源碼學習大家可以 star 我的 github 源碼學習項目(JiayiLi/source-code-study) 進行查看。
underscore 的所有代碼都包裹在匿名自執行函數中,
(function() {n ...n}.call(this)) //通過傳入this(瀏覽器環境中其實就是window對象)來改變函數的作用域n
大多數的源碼設計都是使用的 匿名自執行函數,這樣做的好處:
1、避免全局污染:庫中所定義的變數,方法都封裝到了該函數的作用域中。
2、隱私保護:使用方只能獲得 庫 想暴露在外面的變數方法,而不能訪問 不想暴露的內部變數方法。再說設計之前,這裡還要說一個知識點:
underscore 採用的是典型的函數式編程風格,這與面向對象的編程風格並不相同。函數式編程(fp)風格,設計的函數方法並不會屬於任何一個對象,對象只是 這些函數方法的參數。而面向對象的編程(oop)風格則是 設計的函數方法都隸屬於一個對象。作為對象的一個屬性。如果你還沒有明白,這裡看一下調用方式的不同:函數式編程風格:var arr = [1, 2, 3];n_.map(arr,function(item) {n return item * 2;n});n
arr 這個對象只是 map 方法的一個參數,map 並不屬於 arr。
面向對象風格:
var arr = [1, 2, 3];narr.map(function(item) {ntreturn item * 2;n});n
map 是對象 arr 的一個方法。
看出區別了嗎?
回到匿名自執行函數內部,核心代碼如下:
(function() {ntvar root = this;nnt// 核心函數nt// `_` 其實是一個構造函數ntvar _ = function(obj) {ntt// 以下均針對 OOP 形式的調用ntt// 如果是非 OOP 形式的調用,不會進入該函數內部ntt// 如果 obj 已經是 `_` 函數的實例,則直接返回 objnttif (obj instanceof _) return obj;nntt// 如果不是 `_` 函數的實例ntt// 則調用 new 運算符,返回實例化的對象nttif (! (this instanceof _)) return new _(obj);nntt// 將 obj 賦值給 this._wrapped 屬性nttthis._wrapped = obj;nt};nnt// 將上面定義的 `_` 局部變數賦值給全局對象中的 `_` 屬性nt// 即客戶端中 window._ = _nt// 服務端(node)中 exports._ = _nt// 同時在服務端向後兼容老的 require() APInt// 這樣暴露給全局後便可以在全局環境中使用 `_` 變數(方法)ntif (typeof exports !== undefined) {nttif (typeof module !== undefined && module.exports) {ntttexports = module.exports = _;ntt}nttexports._ = _;nt} else {nttroot._ = _;nt}nnt// ..... 定義工具函數 如 _.each, _.map 等nnt_.mixin = function(obj) {ntt// 遍歷 obj 的 key,將方法掛載到 Underscore 上ntt// 其實是將方法淺拷貝到 _.prototype 上ntt_.each(_.functions(obj),nttfunction(name) {nttt// 直接把方法掛載到 _[name] 上nttt// 調用類似 _.myFunc([1, 2, 3], ..)ntttvar func = _[name] = obj[name];nnttt// 淺拷貝nttt// 將 name 方法掛載到 _ 對象的原型鏈上,使之能 OOP 調用nttt_.prototype[name] = function() {ntttt// 第一個參數nttttvar args = [this._wrapped];nntttt// arguments 為 name 方法需要的其他參數nttttpush.apply(args, arguments);ntttt// 執行 func 方法ntttt// 支持鏈式操作nttttreturn result(this, func.apply(_, args));nttt};ntt});nt};nnt// Add all of the Underscore functions to the wrapper object.nt// 將前面定義的 underscore 方法添加給包裝過的對象nt// 即添加到 _.prototype 中nt// 使 underscore 支持面向對象形式的調用nt_.mixin(_);nnt// Add all mutator Array functions to the wrapper.nt// 將 Array 原型鏈上有的方法都添加到 underscore 中nt_.each([pop, push, reverse, shift, sort, splice, unshift],ntfunction(name) {nttvar method = ArrayProto[name];ntt_.prototype[name] = function() {ntttvar obj = this._wrapped;ntttmethod.apply(obj, arguments);nntttif ((name === shift || name === splice) && obj.length === 0) delete obj[0];nnttt// 支持鏈式操作ntttreturn result(this, obj);ntt};nt});nnt// Add all accessor Array functions to the wrapper.nt// 添加 concat、join、slice 等數組原生方法給 Underscorent_.each([concat, join, slice],ntfunction(name) {nttvar method = ArrayProto[name];ntt_.prototype[name] = function() {ntttreturn result(this, method.apply(this._wrapped, arguments));ntt};nt});nt// 一個包裝過(OOP)並且鏈式調用的對象nt// 用 value 方法獲取結果nt_.prototype.value = function() {nttreturn this._wrapped;nt};nnt_.prototype.valueOf = _.prototype.toJSON = _.prototype.value;nnt_.prototype.toString = function() {nttreturn + this._wrapped;nt};nn}.call(this))n
將 this(瀏覽器環境中其實就是 window 對象)傳入 匿名自執行函數,並賦值給 root。
創建 _ 函數,將其掛到 root 即全局作用域下,故可以全局範圍內使用 _ 。然後在 _ 上定義了工具函數(函數即對象,可以在對象上添加方法),像 _.each, _.map 等。這樣就可以全局使用函數式編程風格的方式 調用 _ 上的方法了, _.each, _.map等。但同時 underscore 也做了針對面向對象風格的調用方式的兼容。需要通過 _() 來包裹一下對象,調用例子:
var arr = [1, 2, 3];n_(arr).map(function(item) {n return item * 2;n});n
var _ = function(obj) {n // 以下均針對 OOP 形式的調用n // 如果是非 OOP 形式的調用,不會進入該函數內部n // 如果 obj 已經是 `_` 函數的實例,則直接返回 objn if (obj instanceof _) return obj;nn // 如果不是 `_` 函數的實例n // 則調用 new 運算符,返回實例化的對象n if (! (this instanceof _)) return new _(obj);nn // 將 obj 賦值給 this._wrapped 屬性n this._wrapped = obj;n};n
如果你採用的是函數式編程風格 調用的話, 不傳 obj ,所以不會進入兩個 if 判斷而直接執行最後一句 this._wrapped = obj;。
如果你採用的是面向對象的編程風格 調用的話,如果 obj 已經是 _ 函數的實例,則直接返回 obj,如果不是 _ 函數的實例, 則調用 new 運算符,返回實例化的對象。那麼 面向對象風格的調用方式,是如何擁有所有定義的方法的呢?
從 代碼中的 _.mixin 函數開始,就是為了兼容 這一種調用。定義了一個 _.mixin 方法 ,並在之後立即執行了,傳入了 _ 作為參數。_.mixin = function(obj) {nt// 遍歷 obj 的 key,將方法掛載到 Underscore 上nt// 其實是將方法淺拷貝到 _.prototype 上nt_.each(_.functions(obj),ntfunction(name) {ntt// 直接把方法掛載到 _[name] 上ntt// 調用類似 _.myFunc([1, 2, 3], ..)nttvar func = _[name] = obj[name];nntt// 淺拷貝ntt// 將 name 方法掛載到 _ 對象的原型鏈上,使之能 OOP 調用ntt_.prototype[name] = function() {nttt// 第一個參數ntttvar args = [this._wrapped];nnttt// arguments 為 name 方法需要的其他參數ntttpush.apply(args, arguments);nttt// 執行 func 方法nttt// 支持鏈式操作ntttreturn result(this, func.apply(_, args));ntt};nt});n};nn// Add all of the Underscore functions to the wrapper object.n// 將前面定義的 underscore 方法添加給包裝過的對象n// 即添加到 _.prototype 中n// 使 underscore 支持面向對象形式的調用n_.mixin(_);nn// Add all mutator Array functions to the wrapper.n// 將 Array 原型鏈上有的方法都添加到 underscore 中n_.each([pop, push, reverse, shift, sort, splice, unshift],nfunction(name) {ntvar method = ArrayProto[name];nt_.prototype[name] = function() {nttvar obj = this._wrapped;nttmethod.apply(obj, arguments);nnttif ((name === shift || name === splice) && obj.length === 0) delete obj[0];nntt// 支持鏈式操作nttreturn result(this, obj);nt};n});nn// Add all accessor Array functions to the wrapper.n// 添加 concat、join、slice 等數組原生方法給 Underscoren_.each([concat, join, slice],nfunction(name) {ntvar method = ArrayProto[name];nt_.prototype[name] = function() {nttreturn result(this, method.apply(this._wrapped, arguments));nt};n});n// 一個包裝過(OOP)並且鏈式調用的對象n// 用 value 方法獲取結果n_.prototype.value = function() {ntreturn this._wrapped;n};nn_.prototype.valueOf = _.prototype.toJSON = _.prototype.value;nn_.prototype.toString = function() {ntreturn + this._wrapped;n};n
_.mixin 函數中將遍歷 _ 的屬性,如果某個屬性的類型是 function,就把該函數掛載到 _ 原型鏈上,這樣對於 _ 函數的實例自然就可以調用 _ 原型鏈上的方法。
對於函數式編程,其實是另一種編程思想,它相較於大家所熟知的面向對象的編程風格來說 ,應該是各有好處。推薦大家看 underscore源碼 和 書 《Javascript函數式編程》 深入了解。
學習並感謝:
underscore.js源碼的組織結構 結構 · undersercore 源碼分析第三部分:backbone 設計分析
backbone 是一個以類 jq 和 underscore 為基礎的 mvc 框架。它是非常好的學習 mvc 框架的入門級 javascript 庫。這裡主要說一下這個框架的設計,而對於細節上的知識點可以 star 我的 github 源碼學習項目(source-code-study) 進行查看。
這裡是刪減了許多代碼的 backbone,只剩下一個外殼:
(function(factory) {n // 定義全局對象 root 變數,在瀏覽器環境下為 window,在 伺服器 環境下為 global, self 指向windown var root = (typeof self == object && self.self === self && self) || (typeof global == object && global.global === global && global);nn // 支持AMD規範n // 使用define函數定義Backbone模塊, 依賴`underscore`, `jquery`, `exports`三個模塊.n if (typeof define === function && define.amd) {n define([underscore, jquery, exports],n function(_, $, exports) {n // Export global even in AMD case in case this script is loaded withn // others that may still expect a global Backbone.n root.Backbone = factory(root, exports, _, $);n });nn // 支持 commonJs 規範(NodeJS使用的規範, 主要用於伺服器端, 所以jQuery非必須).n // CommonJS規範中, exports是用於導出模塊的對象.n } else if (typeof exports !== undefined) {n var _ = require(underscore),n $;n try {n $ = require(jquery);n } catch(e) {}n factory(root, exports, _, $);nn // 以上兩種情況都沒有,則以最簡單的執行函數方式,將函數的返回值作為全局對象Backbonen } else {n root.Backbone = factory(root, {},n root._, (root.jQuery || root.Zepto || root.ender || root.$));n }nn})(function(root, Backbone, _, $) {nn // ……………nn return Backbone;n});n
可以看出來,整個代碼包裝在一個匿名自執行函數中,在對匿名自執行函數立刻調用時,傳的參數也是一個函數,這個函數返回了已經被定義好所有方法的 backbone,作為 factory。之後就對這個 factory 進行了支持各種模塊規範的封裝。這種寫法是非常常見的對於庫或者框架等的封裝方式。
對於常見的模塊規範,以及模塊化載入 大家可以看這篇文章進行學習:【 js 模塊載入 】深入學習模塊化載入(node.js 模塊源碼)
首先定義了 root 變數,在瀏覽器環境下為 window,在 伺服器 環境下為 global,也就是要明確最後要把 backbone 掛在誰的上面。
然後 進入 if 條件循環判斷,來看當前環境支持的是哪種 模塊 規範。
先判斷是否支持AMD(define是否存在),存在則使用AMD方式載入模塊,
如果不支持AMD,則判斷是否支持Node.js的模塊(exports)是否存在,存在則使用Node.js模塊模式,
如果以上兩種都不支持,則直接將函數的返回值掛到全局對象上。
在掛到全局變數的時候,就調用了 factory ,並向其中傳入不同規範所需的參數,分別對應 backbone 函數定義的 root、Backbone、_、$ 參數。
這種對於AMD 規範和Commonjs規範的判斷,其實也有一個名字,叫 umd 規範,即amd和commonjs的綜合判斷。
現在咱們知道了 為何可以在全局使用 backbone 以及它是如何封裝的,咱們再來看看它內部方法的具體實現都用到了些什麼?
(function(factory) {nn})(function(root, Backbone, _, $) {n // Backbone.Eventsn // ---------------n // Backbone 事件部分n // Backbone的 Events 實際上就是一個觀察者模式(發布訂閱模式)的實現,並且巧妙的是,還可以作為mixin混入到自己寫的object中,nn // 初始化Events為一個空對象,js中的對象是按引用傳遞的。n var Events = Backbone.Events = {};nn // Bind an event to a `callback` function. Passing `"all"` will bindn // the callback to all events fired.n // 綁定事件。將一個事件綁定到 `callback` 函數上。事件觸發時執行回調函數`callback`。n Events.on = function(name, callback, context) {nn };nn // 「on」的控制反轉版本。n // 讓 object 監聽 另一個(other)對象上的一個特定事件。跟蹤它正在監聽的內容,以便以後解除綁定。n Events.listenTo = function(obj, name, callback) {nn };nn // 此函數作用於刪除一個或多個回調。n Events.off = function(name, callback, context) {nn };nn // 解除 當前 object 監聽的 其他對象上制定事件,或者說是所有當前監聽的事件n // 如果 傳入被監聽對象 obj 就是解除特定對象上的事件,沒有就是解除所有事件n Events.stopListening = function(obj, name, callback) {nn };nn // 綁定事件只能觸發一次。在第一次調用回調之後,它的監聽器將被刪除。如果使用空格分隔的語法傳遞多個事件,則處理程序將針對每個事件觸發一次,而不是一次所有事件的組合。n Events.once = function(name, callback, context) {nn };nn // once的反轉控制版本n Events.listenToOnce = function(obj, name, callback) {nn };nn // 觸發一個或者多個事件,並觸發所有的回調函數n Events.trigger = function(name) {nn };nn // 將Events的特性全部extend到Backbone, 即Backbone也可以做Backbone.on/Backbone.trigger這樣的操作.n // underscore:_.extend(destination, *sources) 複製source對象中的所有屬性覆蓋到destination對象上,並且返回 destination 對象. 複製是按順序的, 所以後面的對象屬性會把前面的對象屬性覆蓋掉(如果有重複).n _.extend(Backbone, Events);nn // 至此,Events部分結束,接下來是Model部分n // Backbone.Model 模型Model綁定鍵值數據和自定義事件;n // --------------n // 每當一個模型建立,一個 cid 便會被自動創建n // 實際上,Model 函數內的語句順序也是很重要的,這個不能隨便打亂順序(初始化過程)nn // 創建具有指定屬性的新model。 客戶端ID(`cid`)自動生成並分配給您。n var Model = Backbone.Model = function(attributes, options) {nn // Model的唯一的id,這和自己傳入的id並不一樣,雖然我們也要保證id是唯一的n this.cid = _.uniqueId(this.cidPrefix);nn // model 模型元數據都存儲在`attributes`變數中.n this.attributes = {};nn // 如果指定`collection`則保存, model 在構造 url 時可能會用到此參數.n if (options.collection) this.collection = options.collection;nn // 如果之後 new 的時候傳入的是 JSON,我們必須在 options 選項中聲明 parse 為 truen if (options.parse) attrs = this.parse(attrs, options) || {};nn // 存儲歷史變化記錄,用於保存上一次`set`之後改變的數據欄位.n this.changed = {};n // 調用initialize初始化方法。這個initialize也是空的,給初始化之後調用n this.initialize.apply(this, arguments);n };nn // 使用extend方法為Model原型定義一系列屬性和方法。n _.extend(Model.prototype, Events, {n // preinitialize默認為空函數。您可以使用函數或對象覆蓋它。在模型中運行任何實例邏輯之前,preinitialize將運行。n preinitialize: function() {},nn // 默認情況下,Initialize是一個空的函數。用自己的初始化邏輯覆蓋它。n initialize: function() {},nn // Get the value of an attribute.n // 從當前model中獲取當前屬性(attributes)值,比如: note.get("title")n get: function(attr) {n return this.attributes[attr];n },nn // 檢查模型中是否存在某個屬性。屬性值為非 null 或非 undefined 時返回 true。n // 當該屬性的值被轉換為Boolean類型後值為false, 則認為不存在。如果值為false, null, undefined, 0, NaN, 或空字元串時, 均會被轉換為false。n has: function(attr) {n return this.get(attr) != null;n },nn // 向 model 設置一個或多個 hash 屬性(attributes)。如果任何一個屬性改變了 model 的狀態,在不傳入 {silent: true} 選項參數的情況下,會觸發 "change" 事件,更改特定屬性的事件也會觸發。 可以綁定事件到某個屬性,例如:change:title,及 change:content。n set: function(key, val, options) {nn },nn // 從 model 中刪除所有屬性, 包括id屬性。 如果未設置 silent 選項,會觸發 "change" 事件。unset 設置為 true,為刪除操作。n clear: function(options) {nn },nn // 標識模型從上次 set 事件發生後是否改變過。 如果傳入 att ,當指定屬性改變後返回 true。n hasChanged: function(attr) {nn },nn // 如果伺服器已經持久化,則在伺服器上銷毀此模型。如果有模型,則可以從Collection集合中刪除該模型。 如果設置了「wait:true」,等待伺服器成功響應再刪除。n // 如果模型是在客戶端新建的, 則直接從客戶端刪除n // 如果模型數據同時存在伺服器, 則同時會刪除伺服器端的數據n destroy: function(options) {nn }n });nn // 至此,Model部分結束,接下來是Collection部分n // Backbone.Collection 集合Colection是模型的有序或無序集合,帶有豐富的可枚舉API;n // -------------------nn // 如果 model 更傾向於表示單行數據,那麼 Backbone Collection 更類似於完整數據的表,或者表數據的一小片或者一頁表格,或者因為一些原因而聚集在一起的多條行數據。Collections 維護其模型的索引,無論是按順序的還是通過「id」進行查找。nn // 創建一個新的 **Collection**,可能包含了某一種特定的類型的 model。如果指定了「comparator」,則Collection將按照排序順序維護其模型,當它們被添加和刪除。n var Collection = Backbone.Collection = function(models, options) {n // 配置對象n options || (options = {});n // 提前初始化n this.preinitialize.apply(this, arguments);nn // 實例化時重置集合的內部狀態(第一次調用時可理解為定義狀態)n this._reset();n };nn // 通過extend方法定義集合類原型方法n _.extend(Collection.prototype, Events, {nn // preinitialize默認為空函數。你可以用函數或者對象重寫它。preinitialize將在Collection中運行任何實例邏輯之前,先運行。n preinitialize: function() {},nn // initialize 默認也是空函數。你可以用你自己的初始化函數覆蓋它。n initialize: function() {},nn // 向集合中增加一個模型(或一個模型數組),觸發"add"事件。 n add: function(models, options) {nn },nn // 從集合中刪除一個模型(或一個模型數組),會觸發 "remove" 事件,n remove: function(models, options) {nn },nnn // 通過「set」更新集合,添加新的模型,刪除不再存在的模型,並根據需要合併集合中已存在的模型。 類似於** Model#set **,用於更新集合所包含的數據的核心操作。n set: function(models, options) {nn },nn // 添加一個 model 到 collection 集合的最後。n push: function(model, options) {nn },nn // 刪除並且返回集合中最後一個模型。選項和remove相同。n pop: function(options) {nn }nn });nn // Backbone.View 視圖相關n // -------------nn // 視圖類用於創建與數據低耦合的界面控制對象, 通過將視圖的渲染方法綁定到數據模型的change事件, 當數據發生變化時會通知視圖進行渲染n // 視圖對象中的el用於存儲當前視圖所需要操作的DOM最父層元素, 這主要是為了提高元素的查找和操作效率, 其優點包括:n // - 查找或操作元素時, 將操作的範圍限定在el元素內, 不需要再整個文檔樹中搜索n // - 在為元素綁定事件時, 可以方便地將事件綁定到el元素(默認也會綁定到el元素)或者是其子元素n // - 在設計模式中, 將一個視圖相關的元素, 事件, 和邏輯限定在該視圖的範圍中, 降低視圖與視圖間的耦合(至少在邏輯上是這樣)n var View = Backbone.View = function(options) {n // 為每一個視圖對象創建一個唯一標識, 前綴為"view"n this.cid = _.uniqueId(view);n // 提前初始化n this.preinitialize.apply(this, arguments);n // underscore _.pick(object, *keys)方法:返回一個object副本,只過濾出keys(有效的鍵組成的數組)參數指定的屬性值。或者接受一個判斷函數,指定挑選哪個key。n _.extend(this, _.pick(options, viewOptions));n // 初始化dom元素和jQuery元素工作,設置或創建視圖中的元素n this._ensureElement();n // 調用自定義的初始化方法n this.initialize.apply(this, arguments);n };nn // 設置所有可繼承的** Backbone.View **屬性和方法。n _.extend(View.prototype, Events, {nn // 默認空函數。可重寫。n initialize: function() {},nn // ** render **是您的視圖應該覆蓋的核心函數,以便使用適當的HTML填充其元素(`this.el`)。這個慣例是** render **總是返回`this`。n render: function() {n return this;n },nn //移除這個View。n //通過將元素從DOM中移除,並刪除任何適用的Backbone.Events偵聽器來刪除此視圖。n remove: function() {nn },nn // 如果你想應用一個Backbone視圖到不同的DOM元素, 使用setElement, 這也將創造緩存$el引用,視圖的委託事件從舊元素移動到新元素上。n // 更改視圖的元素(`this.el`屬性)並重新委派新元素上的視圖事件。n setElement: function(element) {nn }n });nn // Backbone.sync Backbone 每次向伺服器讀取或保存模型時都要調用執行的函數。n // -------------n // 覆蓋此功能以更改Backbone將models持續到伺服器的方式。你需要傳遞 request 的類型以及有問題的 model。默認情況下,一個 RESTful Ajax 請求會調用 model 的 url() 方法。一些可能的使用場景:n // 1、使用 setTimeout 將快速更新 批量導入到單個請求中。n // 2、發送 XML 形式的 modeln // 3、通過WebSockets而不是Ajax來持久化模型。n Backbone.sync = function(method, model, options) {nn };nn // Backbone.Router Backbone的路由部分,這部分被認為是backbone的MVC結構中的被弱化的controllern // ---------------nn var Router = Backbone.Router = function(options) {n options || (options = {});n // 提前初始化n this.preinitialize.apply(this, arguments);n // 如果在options中設置了routes對象(路由規則), 則賦給當前實例的routes屬性n // routes屬性記錄了路由規則與事件方法的綁定關係, 當URL與某一個規則匹配時, 會自動調用關聯的事件方法n if (options.routes) this.routes = options.routes;n // 解析和綁定路由規則n this._bindRoutes();n // 調用自定義的初始化方法n this.initialize.apply(this, arguments);n };nn // 向Router類的原型對象中擴展屬性和方法n _.extend(Router.prototype, Events, {nn // 默認為空函數,可以重寫,在路由器中運行任何實例化邏輯之前,preinitialize將先運行。n preinitialize: function() {},nn // 自定義初始化方法, 在路由器Router實例化後被自動調用n initialize: function() {},nn // 將一個路由規則綁定給一個監聽事件, 當URL片段匹配該規則時, 會自動調用觸發該事件。n route: function(route, name, callback) {nn },nn // 在route方法內部被調用, 每當路由和其相應的callback匹配時被執行。 覆蓋它來執行自定義解析或包裝路由。n execute: function(callback, args, name) {nn },nnn // 每當你達到你的應用的一個點時,你想保存為一個URL, 可以調用navigate以更新的URL。 n navigate: function(fragment, options) {nn }n });nn // Backbone.History 路由器管理n // ----------------n // n // Backbone的history是通過綁定hashchange事件的監聽來監聽網頁url的變化(通過popstate和onhashchange事件進行監聽, 對於不支持事件的瀏覽器通過setInterval心跳監控),從而調用相關函數n // 另外,在不支持hashchange事件的瀏覽器中,採用輪詢的方式n var History = Backbone.History = function() {nn // handlers屬性記錄了當前所有路由對象中已經設置的規則和監聽列表n // 形式如: [{route: route, callback: callback}], route記錄了正則表達式規則, callback記錄了匹配規則時的監聽事件n // 當history對象監聽到URL發生變化時, 會自動與handlers中定義的規則進行匹配, 並調用監聽事件n this.handlers = [];nn this.checkUrl = _.bind(this.checkUrl, this);n };nn // 向History類的原型對象中添加方法, 這些方法可以通過History的實例調用(即Backbone.history對象)n _.extend(History.prototype, Events, {nn // 如果處於根節點那麼this.location.pathname獲取到的應該是`/`n // 另外這裡用到了getSearch來獲取?後面的內容,如果能獲取到自然說明並不是在根節點n atRoot: function() {n var path = this.location.pathname.replace(/[^/]$/, $&/);n return path === this.root && !this.getSearch();n },nn // 防止像%這樣的可能是參數的一部分的符號被編碼 ????n decodeFragment: function(fragment) {n return decodeURI(fragment.replace(/%25/g, %2525));n },nn // 取得?以及其後面的內容n getSearch: function() {n var match = this.location.href.replace(/#.*/, ).match(/?.+/);n return match ? match[0] : ;n },nn // 停止history對路由的監控, 並將狀態恢復為未監聽狀態n // 調用stop方法之後, 可重新調用start方法開始監聽, stop方法一般用戶在調用start方法之後, 需要重新設置start方法的參數, 或用於單元測試n stop: function() {nn },nn // 檢查當前的URL相對上一次的狀態是否發生了變化n checkUrl: function(e) {nn },nn // 導航到指定的URL,存儲/更新歷史記錄n navigate: function(fragment, options) {nn },n });nn // Create the default Backbone.history.n Backbone.history = new History;nn return Backbone;n});n
粗略的過一下,可以看出來backbone大部分模塊,像events、model、view等都採用的是原型繼承的方式,使用構造函數來定義一些不同實例自己獨有的變數,方法以及調用一些初始化方法,而使用 prototype,也就是原型來定義所有實例都有的方法,變數。除了 最先定義的 events 模塊,後面的模塊(除了sync模塊),在用原型定義通用方法的時候,先調用 extends 方法將 Events 模塊上的方法複製到了當前模塊,這樣模塊就享有了events 模塊上的所有方法,這樣就可以方便不同模塊在有需要的時候監聽或者調用某些方法。
對於繼承,大家可以看我的這一篇文章進行學習: 【 js 基礎 】Javascript 「繼承」
以上。
文章會持續更新。歡迎大家 star 我的 github 閱讀源碼項目(JiayiLi/source-code-study),進行關注。
推薦閱讀:
