Backbone.js 0.9.2 源碼注釋中文翻譯版
更新時(shí)間:2015年06月25日 09:11:19 投稿:junjie
Backbone.js為復(fù)雜WEB應(yīng)用程序提供模型(models)、集合(collections)、視圖(views)的結(jié)構(gòu)。其中模型用于綁定鍵值數(shù)據(jù)和自定義事件;集合附有可枚舉函數(shù)的豐富API; 視圖可以聲明事件處理函數(shù),并通過(guò)RESRful JSON接口連接到應(yīng)用程序。
// Backbone.js 0.9.2
// (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc.
// Backbone may be freely distributed under the MIT license.
// For all details and documentation:
// http://backbonejs.org
(function() {
// 創(chuàng)建一個(gè)全局對(duì)象, 在瀏覽器中表示為window對(duì)象, 在Node.js中表示global對(duì)象
var root = this;
// 保存"Backbone"變量被覆蓋之前的值
// 如果出現(xiàn)命名沖突或考慮到規(guī)范, 可通過(guò)Backbone.noConflict()方法恢復(fù)該變量被Backbone占用之前的值, 并返回Backbone對(duì)象以便重新命名
var previousBackbone = root.Backbone;
// 將Array.prototype中的slice和splice方法緩存到局部變量以供調(diào)用
var slice = Array.prototype.slice;
var splice = Array.prototype.splice;
var Backbone;
if( typeof exports !== 'undefined') {
Backbone = exports;
} else {
Backbone = root.Backbone = {};
}
// 定義Backbone版本
Backbone.VERSION = '0.9.2';
// 在服務(wù)器環(huán)境下自動(dòng)導(dǎo)入U(xiǎn)nderscore, 在Backbone中部分方法依賴或繼承自Underscore
var _ = root._;
if(!_ && ( typeof require !== 'undefined'))
_ = require('underscore');
// 定義第三方庫(kù)為統(tǒng)一的變量"$", 用于在視圖(View), 事件處理和與服務(wù)器數(shù)據(jù)同步(sync)時(shí)調(diào)用庫(kù)中的方法
// 支持的庫(kù)包括jQuery, Zepto等, 它們語(yǔ)法相同, 但Zepto更適用移動(dòng)開(kāi)發(fā), 它主要針對(duì)Webkit內(nèi)核瀏覽器
// 也可以通過(guò)自定義一個(gè)與jQuery語(yǔ)法相似的自定義庫(kù), 供Backbone使用(有時(shí)我們可能需要一個(gè)比jQuery, Zepto更輕巧的自定義版本)
// 這里定義的"$"是局部變量, 因此不會(huì)影響在Backbone框架之外第三方庫(kù)的正常使用
var $ = root.jQuery || root.Zepto || root.ender;
// 手動(dòng)設(shè)置第三方庫(kù)
// 如果在導(dǎo)入了Backbone之前并沒(méi)有導(dǎo)入第三方庫(kù), 可以通過(guò)setDomLibrary方法設(shè)置"$"局部變量
// setDomLibrary方法也常用于在Backbone中動(dòng)態(tài)導(dǎo)入自定義庫(kù)
Backbone.setDomLibrary = function(lib) {
$ = lib;
};
// 放棄以"Backbone"命名框架, 并返回Backbone對(duì)象, 一般用于避免命名沖突或規(guī)范命名方式
// 例如:
// var bk = Backbone.noConflict(); // 取消"Backbone"命名, 并將Backbone對(duì)象存放于bk變量中
// console.log(Backbone); // 該變量已經(jīng)無(wú)法再訪問(wèn)Backbone對(duì)象, 而恢復(fù)為Backbone定義前的值
// var MyBackbone = bk; // 而bk存儲(chǔ)了Backbone對(duì)象, 我們將它重命名為MyBackbone
Backbone.noConflict = function() {
root.Backbone = previousBackbone;
return this;
};
// 對(duì)于不支持REST方式的瀏覽器, 可以設(shè)置Backbone.emulateHTTP = true
// 與服務(wù)器請(qǐng)求將以POST方式發(fā)送, 并在數(shù)據(jù)中加入_method參數(shù)標(biāo)識(shí)操作名稱, 同時(shí)也將發(fā)送X-HTTP-Method-Override頭信息
Backbone.emulateHTTP = false;
// 對(duì)于不支持application/json編碼的瀏覽器, 可以設(shè)置Backbone.emulateJSON = true;
// 將請(qǐng)求類型設(shè)置為application/x-www-form-urlencoded, 并將數(shù)據(jù)放置在model參數(shù)中實(shí)現(xiàn)兼容
Backbone.emulateJSON = false;
// Backbone.Events 自定義事件相關(guān)
// -----------------
// eventSplitter指定處理多個(gè)事件時(shí), 事件名稱的解析規(guī)則
var eventSplitter = /\s+/;
// 自定義事件管理器
// 通過(guò)在對(duì)象中綁定Events相關(guān)方法, 允許向?qū)ο筇砑? 刪除和觸發(fā)自定義事件
var Events = Backbone.Events = {
// 將自定義事件(events)和回調(diào)函數(shù)(callback)綁定到當(dāng)前對(duì)象
// 回調(diào)函數(shù)中的上下文對(duì)象為指定的context, 如果沒(méi)有設(shè)置context則上下文對(duì)象默認(rèn)為當(dāng)前綁定事件的對(duì)象
// 該方法類似與DOM Level2中的addEventListener方法
// events允許指定多個(gè)事件名稱, 通過(guò)空白字符進(jìn)行分隔(如空格, 制表符等)
// 當(dāng)事件名稱為"all"時(shí), 在調(diào)用trigger方法觸發(fā)任何事件時(shí), 均會(huì)調(diào)用"all"事件中綁定的所有回調(diào)函數(shù)
on : function(events, callback, context) {
// 定義一些函數(shù)中使用到的局部變量
var calls, event, node, tail, list;
// 必須設(shè)置callback回調(diào)函數(shù)
if(!callback)
return this;
// 通過(guò)eventSplitter對(duì)事件名稱進(jìn)行解析, 使用split將多個(gè)事件名拆分為一個(gè)數(shù)組
// 一般使用空白字符指定多個(gè)事件名稱
events = events.split(eventSplitter);
// calls記錄了當(dāng)前對(duì)象中已綁定的事件與回調(diào)函數(shù)列表
calls = this._callbacks || (this._callbacks = {});
// 循環(huán)事件名列表, 從頭至尾依次將事件名存放至event變量
while( event = events.shift()) {
// 獲取已經(jīng)綁定event事件的回調(diào)函數(shù)
// list存儲(chǔ)單個(gè)事件名中綁定的callback回調(diào)函數(shù)列表
// 函數(shù)列表并沒(méi)有通過(guò)數(shù)組方式存儲(chǔ), 而是通過(guò)多個(gè)對(duì)象的next屬性進(jìn)行依次關(guān)聯(lián)
/** 數(shù)據(jù)格式如:
* {
* tail: {Object},
* next: {
* callback: {Function},
* context: {Object},
* next: {
* callback: {Function},
* context: {Object},
* next: {Object}
* }
* }
* }
*/
// 列表每一層next對(duì)象存儲(chǔ)了一次回調(diào)事件相關(guān)信息(函數(shù)體, 上下文和下一次回調(diào)事件)
// 事件列表最頂層存儲(chǔ)了一個(gè)tail對(duì)象, 它存儲(chǔ)了最后一次綁定回調(diào)事件的標(biāo)識(shí)(與最后一次回調(diào)事件的next指向同一個(gè)對(duì)象)
// 通過(guò)tail標(biāo)識(shí), 可以在遍歷回調(diào)列表時(shí)得知已經(jīng)到達(dá)最后一個(gè)回調(diào)函數(shù)
list = calls[event];
// node變量用于記錄本次回調(diào)函數(shù)的相關(guān)信息
// tail只存儲(chǔ)最后一次綁定回調(diào)函數(shù)的標(biāo)識(shí)
// 因此如果之前已經(jīng)綁定過(guò)回調(diào)函數(shù), 則將之前的tail指定給node作為一個(gè)對(duì)象使用, 然后創(chuàng)建一個(gè)新的對(duì)象標(biāo)識(shí)給tail
// 這里之所以要將本次回調(diào)事件添加到上一次回調(diào)的tail對(duì)象, 是為了讓回調(diào)函數(shù)列表的對(duì)象層次關(guān)系按照綁定順序排列(最新綁定的事件將被放到最底層)
node = list ? list.tail : {};
node.next = tail = {};
// 記錄本次回調(diào)的函數(shù)體及上下文信息
node.context = context;
node.callback = callback;
// 重新組裝當(dāng)前事件的回調(diào)列表, 列表中已經(jīng)加入了本次回調(diào)事件
calls[event] = {
tail : tail,
next : list ? list.next : node
};
}
// 返回當(dāng)前對(duì)象, 方便進(jìn)行方法鏈調(diào)用
return this;
},
// 移除對(duì)象中已綁定的事件或回調(diào)函數(shù), 可以通過(guò)events, callback和context對(duì)需要?jiǎng)h除的事件或回調(diào)函數(shù)進(jìn)行過(guò)濾
// - 如果context為空, 則移除所有的callback指定的函數(shù)
// - 如果callback為空, 則移除事件中所有的回調(diào)函數(shù)
// - 如果events為空, 但指定了callback或context, 則移除callback或context指定的回調(diào)函數(shù)(不區(qū)分事件名稱)
// - 如果沒(méi)有傳遞任何參數(shù), 則移除對(duì)象中綁定的所有事件和回調(diào)函數(shù)
off : function(events, callback, context) {
var event, calls, node, tail, cb, ctx;
// No events, or removing *all* events.
// 當(dāng)前對(duì)象沒(méi)有綁定任何事件
if(!( calls = this._callbacks))
return;
// 如果沒(méi)有指定任何參數(shù), 則移除所有事件和回調(diào)函數(shù)(刪除_callbacks屬性)
if(!(events || callback || context)) {
delete this._callbacks;
return this;
}
// 解析需要移除的事件列表
// - 如果指定了events, 則按照eventSplitter對(duì)事件名進(jìn)行解析
// - 如果沒(méi)有指定events, 則解析已綁定所有事件的名稱列表
events = events ? events.split(eventSplitter) : _.keys(calls);
// 循環(huán)事件名列表
while( event = events.shift()) {
// 將當(dāng)前事件對(duì)象從列表中移除, 并緩存到node變量中
node = calls[event];
delete calls[event];
// 如果不存在當(dāng)前事件對(duì)象(或沒(méi)有指定移除過(guò)濾條件, 則認(rèn)為將移除當(dāng)前事件及所有回調(diào)函數(shù)), 則終止此次操作(事件對(duì)象在上一步已經(jīng)移除)
if(!node || !(callback || context))
continue;
// Create a new list, omitting the indicated callbacks.
// 根據(jù)回調(diào)函數(shù)或上下文過(guò)濾條件, 組裝一個(gè)新的事件對(duì)象并重新綁定
tail = node.tail;
// 遍歷事件中的所有回調(diào)對(duì)象
while(( node = node.next) !== tail) {
cb = node.callback;
ctx = node.context;
// 根據(jù)參數(shù)中的回調(diào)函數(shù)和上下文, 對(duì)回調(diào)函數(shù)進(jìn)行過(guò)濾, 將不符合過(guò)濾條件的回調(diào)函數(shù)重新綁定到事件中(因?yàn)槭录械乃谢卣{(diào)函數(shù)在上面已經(jīng)被移除)
if((callback && cb !== callback) || (context && ctx !== context)) {
this.on(event, cb, ctx);
}
}
}
return this;
},
// 觸發(fā)已經(jīng)定義的一個(gè)或多個(gè)事件, 依次執(zhí)行綁定的回調(diào)函數(shù)列表
trigger : function(events) {
var event, node, calls, tail, args, all, rest;
// 當(dāng)前對(duì)象沒(méi)有綁定任何事件
if(!( calls = this._callbacks))
return this;
// 獲取回調(diào)函數(shù)列表中綁定的"all"事件列表
all = calls.all;
// 將需要觸發(fā)的事件名稱, 按照eventSplitter規(guī)則解析為一個(gè)數(shù)組
events = events.split(eventSplitter);
// 將trigger從第2個(gè)之后的參數(shù), 記錄到rest變量, 將依次傳遞給回調(diào)函數(shù)
rest = slice.call(arguments, 1);
// 循環(huán)需要觸發(fā)的事件列表
while( event = events.shift()) {
// 此處的node變量記錄了當(dāng)前事件的所有回調(diào)函數(shù)列表
if( node = calls[event]) {
// tail變量記錄最后一次綁定事件的對(duì)象標(biāo)識(shí)
tail = node.tail;
// node變量的值, 按照事件的綁定順序, 被依次賦值為綁定的單個(gè)回調(diào)事件對(duì)象
// 最后一次綁定的事件next屬性, 與tail引用同一個(gè)對(duì)象, 以此作為是否到達(dá)列表末尾的判斷依據(jù)
while(( node = node.next) !== tail) {
// 執(zhí)行所有綁定的事件, 并將調(diào)用trigger時(shí)的參數(shù)傳遞給回調(diào)函數(shù)
node.callback.apply(node.context || this, rest);
}
}
// 變量all記錄了綁定時(shí)的"all"事件, 即在調(diào)用任何事件時(shí), "all"事件中的回調(diào)函數(shù)均會(huì)被執(zhí)行
// - "all"事件中的回調(diào)函數(shù)無(wú)論綁定順序如何, 都會(huì)在當(dāng)前事件的回調(diào)函數(shù)列表全部執(zhí)行完畢后再依次執(zhí)行
// - "all"事件應(yīng)該在觸發(fā)普通事件時(shí)被自動(dòng)調(diào)用, 如果強(qiáng)制觸發(fā)"all"事件, 事件中的回調(diào)函數(shù)將被執(zhí)行兩次
if( node = all) {
tail = node.tail;
// 與調(diào)用普通事件的回調(diào)函數(shù)不同之處在于, all事件會(huì)將當(dāng)前調(diào)用的事件名作為第一個(gè)參數(shù)傳遞給回調(diào)函數(shù)
args = [event].concat(rest);
// 遍歷并執(zhí)行"all"事件中的回調(diào)函數(shù)列表
while(( node = node.next) !== tail) {
node.callback.apply(node.context || this, args);
}
}
}
return this;
}
};
// 綁定事件與釋放事件的別名, 也為了同時(shí)兼容Backbone以前的版本
Events.bind = Events.on;
Events.unbind = Events.off;
// Backbone.Model 數(shù)據(jù)對(duì)象模型
// --------------
// Model是Backbone中所有數(shù)據(jù)對(duì)象模型的基類, 用于創(chuàng)建一個(gè)數(shù)據(jù)模型
// @param {Object} attributes 指定創(chuàng)建模型時(shí)的初始化數(shù)據(jù)
// @param {Object} options
/**
* @format options
* {
* parse: {Boolean},
* collection: {Collection}
* }
*/
var Model = Backbone.Model = function(attributes, options) {
// defaults變量用于存儲(chǔ)模型的默認(rèn)數(shù)據(jù)
var defaults;
// 如果沒(méi)有指定attributes參數(shù), 則設(shè)置attributes為空對(duì)象
attributes || ( attributes = {});
// 設(shè)置attributes默認(rèn)數(shù)據(jù)的解析方法, 例如默認(rèn)數(shù)據(jù)是從服務(wù)器獲取(或原始數(shù)據(jù)是XML格式), 為了兼容set方法所需的數(shù)據(jù)格式, 可使用parse方法進(jìn)行解析
if(options && options.parse)
attributes = this.parse(attributes);
if( defaults = getValue(this, 'defaults')) {
// 如果Model在定義時(shí)設(shè)置了defaults默認(rèn)數(shù)據(jù), 則初始化數(shù)據(jù)使用defaults與attributes參數(shù)合并后的數(shù)據(jù)(attributes中的數(shù)據(jù)會(huì)覆蓋defaults中的同名數(shù)據(jù))
attributes = _.extend({}, defaults, attributes);
}
// 顯式指定模型所屬的Collection對(duì)象(在調(diào)用Collection的add, push等將模型添加到集合中的方法時(shí), 會(huì)自動(dòng)設(shè)置模型所屬的Collection對(duì)象)
if(options && options.collection)
this.collection = options.collection;
// attributes屬性存儲(chǔ)了當(dāng)前模型的JSON對(duì)象化數(shù)據(jù), 創(chuàng)建模型時(shí)默認(rèn)為空
this.attributes = {};
// 定義_escapedAttributes緩存對(duì)象, 它將緩存通過(guò)escape方法處理過(guò)的數(shù)據(jù)
this._escapedAttributes = {};
// 為每一個(gè)模型配置一個(gè)唯一標(biāo)識(shí)
this.cid = _.uniqueId('c');
// 定義一系列用于記錄數(shù)據(jù)狀態(tài)的對(duì)象, 具體含義請(qǐng)參考對(duì)象定義時(shí)的注釋
this.changed = {};
this._silent = {};
this._pending = {};
// 創(chuàng)建實(shí)例時(shí)設(shè)置初始化數(shù)據(jù), 首次設(shè)置使用silent參數(shù), 不會(huì)觸發(fā)change事件
this.set(attributes, {
silent : true
});
// 上面已經(jīng)設(shè)置了初始化數(shù)據(jù), changed, _silent, _pending對(duì)象的狀態(tài)可能已經(jīng)發(fā)生變化, 這里重新進(jìn)行初始化
this.changed = {};
this._silent = {};
this._pending = {};
// _previousAttributes變量存儲(chǔ)模型數(shù)據(jù)的一個(gè)副本
// 用于在change事件中獲取模型數(shù)據(jù)被改變之前的狀態(tài), 可通過(guò)previous或previousAttributes方法獲取上一個(gè)狀態(tài)的數(shù)據(jù)
this._previousAttributes = _.clone(this.attributes);
// 調(diào)用initialize初始化方法
this.initialize.apply(this, arguments);
};
// 使用extend方法為Model原型定義一系列屬性和方法
_.extend(Model.prototype, Events, {
// changed屬性記錄了每次調(diào)用set方法時(shí), 被改變數(shù)據(jù)的key集合
changed : null,
// // 當(dāng)指定silent屬性時(shí), 不會(huì)觸發(fā)change事件, 被改變的數(shù)據(jù)會(huì)記錄下來(lái), 直到下一次觸發(fā)change事件
// _silent屬性用來(lái)記錄使用silent時(shí)的被改變的數(shù)據(jù)
_silent : null,
_pending : null,
// 每個(gè)模型的唯一標(biāo)識(shí)屬性(默認(rèn)為"id", 通過(guò)修改idAttribute可自定義id屬性名)
// 如果在設(shè)置數(shù)據(jù)時(shí)包含了id屬性, 則id將會(huì)覆蓋模型的id
// id用于在Collection集合中查找和標(biāo)識(shí)模型, 與后臺(tái)接口通信時(shí)也會(huì)以id作為一條記錄的標(biāo)識(shí)
idAttribute : 'id',
// 模型初始化方法, 在模型被構(gòu)造結(jié)束后自動(dòng)調(diào)用
initialize : function() {
},
// 返回當(dāng)前模型中數(shù)據(jù)的一個(gè)副本(JSON對(duì)象格式)
toJSON : function(options) {
return _.clone(this.attributes);
},
// 根據(jù)attr屬性名, 獲取模型中的數(shù)據(jù)值
get : function(attr) {
return this.attributes[attr];
},
// 根據(jù)attr屬性名, 獲取模型中的數(shù)據(jù)值, 數(shù)據(jù)值包含的HTML特殊字符將被轉(zhuǎn)換為HTML實(shí)體, 包含 & < > " ' \
// 通過(guò) _.escape方法實(shí)現(xiàn)
escape : function(attr) {
var html;
// 從_escapedAttributes緩存對(duì)象中查找數(shù)據(jù), 如果數(shù)據(jù)已經(jīng)被緩存則直接返回
if( html = this._escapedAttributes[attr])
return html;
// _escapedAttributes緩存對(duì)象中沒(méi)有找到數(shù)據(jù)
// 則先從模型中獲取數(shù)據(jù)
var val = this.get(attr);
// 將數(shù)據(jù)中的HTML使用 _.escape方法轉(zhuǎn)換為實(shí)體, 并緩存到_escapedAttributes對(duì)象, 便于下次直接獲取
return this._escapedAttributes[attr] = _.escape(val == null ? '' : '' + val);
},
// 檢查模型中是否存在某個(gè)屬性, 當(dāng)該屬性的值被轉(zhuǎn)換為Boolean類型后值為false, 則認(rèn)為不存在
// 如果值為false, null, undefined, 0, NaN, 或空字符串時(shí), 均會(huì)被轉(zhuǎn)換為false
has : function(attr) {
return this.get(attr) != null;
},
// 設(shè)置模型中的數(shù)據(jù), 如果key值不存在, 則作為新的屬性添加到模型, 如果key值已經(jīng)存在, 則修改為新的值
set : function(key, value, options) {
// attrs變量中記錄需要設(shè)置的數(shù)據(jù)對(duì)象
var attrs, attr, val;
// 參數(shù)形式允許key-value對(duì)象形式, 或通過(guò)key, value兩個(gè)參數(shù)進(jìn)行單獨(dú)設(shè)置
// 如果key是一個(gè)對(duì)象, 則認(rèn)定為使用對(duì)象形式設(shè)置, 第二個(gè)參數(shù)將被視為options參數(shù)
if(_.isObject(key) || key == null) {
attrs = key;
options = value;
} else {
// 通過(guò)key, value兩個(gè)參數(shù)單獨(dú)設(shè)置, 將數(shù)據(jù)放到attrs對(duì)象中方便統(tǒng)一處理
attrs = {};
attrs[key] = value;
}
// options配置項(xiàng)必須是一個(gè)對(duì)象, 如果沒(méi)有設(shè)置options則默認(rèn)值為一個(gè)空對(duì)象
options || ( options = {});
// 沒(méi)有設(shè)置參數(shù)時(shí)不執(zhí)行任何動(dòng)作
if(!attrs)
return this;
// 如果被設(shè)置的數(shù)據(jù)對(duì)象屬于Model類的一個(gè)實(shí)例, 則將Model對(duì)象的attributes數(shù)據(jù)對(duì)象賦給attrs
// 一般在復(fù)制一個(gè)Model對(duì)象的數(shù)據(jù)到另一個(gè)Model對(duì)象時(shí), 會(huì)執(zhí)行該動(dòng)作
if( attrs instanceof Model)
attrs = attrs.attributes;
// 如果options配置對(duì)象中設(shè)置了unset屬性, 則將attrs數(shù)據(jù)對(duì)象中的所有屬性重置為undefined
// 一般在復(fù)制一個(gè)Model對(duì)象的數(shù)據(jù)到另一個(gè)Model對(duì)象時(shí), 但僅僅需要復(fù)制Model中的數(shù)據(jù)而不需要復(fù)制值時(shí)執(zhí)行該操作
if(options.unset)
for(attr in attrs)
attrs[attr] =
void 0;
// 對(duì)當(dāng)前數(shù)據(jù)進(jìn)行驗(yàn)證, 如果驗(yàn)證未通過(guò)則停止執(zhí)行
if(!this._validate(attrs, options))
return false;
// 如果設(shè)置的id屬性名被包含在數(shù)據(jù)集合中, 則將id覆蓋到模型的id屬性
// 這是為了確保在自定義id屬性名后, 訪問(wèn)模型的id屬性時(shí), 也能正確訪問(wèn)到id
if(this.idAttribute in attrs)
this.id = attrs[this.idAttribute];
var changes = options.changes = {};
// now記錄當(dāng)前模型中的數(shù)據(jù)對(duì)象
var now = this.attributes;
// escaped記錄當(dāng)前模型中通過(guò)escape緩存過(guò)的數(shù)據(jù)
var escaped = this._escapedAttributes;
// prev記錄模型中數(shù)據(jù)被改變之前的值
var prev = this._previousAttributes || {};
// 遍歷需要設(shè)置的數(shù)據(jù)對(duì)象
for(attr in attrs) {
// attr存儲(chǔ)當(dāng)前屬性名稱, val存儲(chǔ)當(dāng)前屬性的值
val = attrs[attr];
// 如果當(dāng)前數(shù)據(jù)在模型中不存在, 或已經(jīng)發(fā)生變化, 或在options中指定了unset屬性刪除, 則刪除該數(shù)據(jù)被換存在_escapedAttributes中的數(shù)據(jù)
if(!_.isEqual(now[attr], val) || (options.unset && _.has(now, attr))) {
// 僅刪除通過(guò)escape緩存過(guò)的數(shù)據(jù), 這是為了保證緩存中的數(shù)據(jù)與模型中的真實(shí)數(shù)據(jù)保持同步
delete escaped[attr];
// 如果指定了silent屬性, 則此次set方法調(diào)用不會(huì)觸發(fā)change事件, 因此將被改變的數(shù)據(jù)記錄到_silent屬性中, 便于下一次觸發(fā)change事件時(shí), 通知事件監(jiān)聽(tīng)函數(shù)此數(shù)據(jù)已經(jīng)改變
// 如果沒(méi)有指定silent屬性, 則直接設(shè)置changes屬性中當(dāng)前數(shù)據(jù)為已改變狀態(tài)
(options.silent ? this._silent : changes)[attr] = true;
}
// 如果在options中設(shè)置了unset, 則從模型中刪除該數(shù)據(jù)(包括key)
// 如果沒(méi)有指定unset屬性, 則認(rèn)為將新增或修改數(shù)據(jù), 向模型的數(shù)據(jù)對(duì)象中加入新的數(shù)據(jù)
options.unset ?
delete now[attr] : now[attr] = val;
// 如果模型中的數(shù)據(jù)與新的數(shù)據(jù)不一致, 則表示該數(shù)據(jù)已發(fā)生變化
if(!_.isEqual(prev[attr], val) || (_.has(now, attr) != _.has(prev, attr))) {
// 在changed屬性中記錄當(dāng)前屬性已經(jīng)發(fā)生變化的狀態(tài)
this.changed[attr] = val;
if(!options.silent)
this._pending[attr] = true;
} else {
// 如果數(shù)據(jù)沒(méi)有發(fā)生變化, 則從changed屬性中移除已變化狀態(tài)
delete this.changed[attr];
delete this._pending[attr];
}
}
// 調(diào)用change方法, 將觸發(fā)change事件綁定的函數(shù)
if(!options.silent)
this.change(options);
return this;
},
// 從當(dāng)前模型中刪除指定的數(shù)據(jù)(屬性也將被同時(shí)刪除)
unset : function(attr, options) {
(options || ( options = {})).unset = true;
// 通過(guò)options.unset配置項(xiàng)告知set方法進(jìn)行刪除操作
return this.set(attr, null, options);
},
// 清除當(dāng)前模型中的所有數(shù)據(jù)和屬性
clear : function(options) {
(options || ( options = {})).unset = true;
// 克隆一個(gè)當(dāng)前模型的屬性副本, 并通過(guò)options.unset配置項(xiàng)告知set方法執(zhí)行刪除操作
return this.set(_.clone(this.attributes), options);
},
// 從服務(wù)器獲取默認(rèn)的模型數(shù)據(jù), 獲取數(shù)據(jù)后使用set方法將數(shù)據(jù)填充到模型, 因此如果獲取到的數(shù)據(jù)與當(dāng)前模型中的數(shù)據(jù)不一致, 將會(huì)觸發(fā)change事件
fetch : function(options) {
// 確保options是一個(gè)新的對(duì)象, 隨后將改變options中的屬性
options = options ? _.clone(options) : {};
var model = this;
// 在options中可以指定獲取數(shù)據(jù)成功后的自定義回調(diào)函數(shù)
var success = options.success;
// 當(dāng)獲取數(shù)據(jù)成功后填充數(shù)據(jù)并調(diào)用自定義成功回調(diào)函數(shù)
options.success = function(resp, status, xhr) {
// 通過(guò)parse方法將服務(wù)器返回的數(shù)據(jù)進(jìn)行轉(zhuǎn)換
// 通過(guò)set方法將轉(zhuǎn)換后的數(shù)據(jù)填充到模型中, 因此可能會(huì)觸發(fā)change事件(當(dāng)數(shù)據(jù)發(fā)生變化時(shí))
// 如果填充數(shù)據(jù)時(shí)驗(yàn)證失敗, 則不會(huì)調(diào)用自定義success回調(diào)函數(shù)
if(!model.set(model.parse(resp, xhr), options))
return false;
// 調(diào)用自定義的success回調(diào)函數(shù)
if(success)
success(model, resp);
};
// 請(qǐng)求發(fā)生錯(cuò)誤時(shí)通過(guò)wrapError處理error事件
options.error = Backbone.wrapError(options.error, model, options);
// 調(diào)用sync方法從服務(wù)器獲取數(shù)據(jù)
return (this.sync || Backbone.sync).call(this, 'read', this, options);
},
// 保存模型中的數(shù)據(jù)到服務(wù)器
save : function(key, value, options) {
// attrs存儲(chǔ)需要保存到服務(wù)器的數(shù)據(jù)對(duì)象
var attrs, current;
// 支持設(shè)置單個(gè)屬性的方式 key: value
// 支持對(duì)象形式的批量設(shè)置方式 {key: value}
if(_.isObject(key) || key == null) {
// 如果key是一個(gè)對(duì)象, 則認(rèn)為是通過(guò)對(duì)象方式設(shè)置
// 此時(shí)第二個(gè)參數(shù)被認(rèn)為是options
attrs = key;
options = value;
} else {
// 如果是通過(guò)key: value形式設(shè)置單個(gè)屬性, 則直接設(shè)置attrs
attrs = {};
attrs[key] = value;
}
// 配置對(duì)象必須是一個(gè)新的對(duì)象
options = options ? _.clone(options) : {};
// 如果在options中設(shè)置了wait選項(xiàng), 則被改變的數(shù)據(jù)將會(huì)被提前驗(yàn)證, 且服務(wù)器沒(méi)有響應(yīng)新數(shù)據(jù)(或響應(yīng)失敗)時(shí), 本地?cái)?shù)據(jù)會(huì)被還原為修改前的狀態(tài)
// 如果沒(méi)有設(shè)置wait選項(xiàng), 則無(wú)論服務(wù)器是否設(shè)置成功, 本地?cái)?shù)據(jù)均會(huì)被修改為最新?tīng)顟B(tài)
if(options.wait) {
// 對(duì)需要保存的數(shù)據(jù)提前進(jìn)行驗(yàn)證
if(!this._validate(attrs, options))
return false;
// 記錄當(dāng)前模型中的數(shù)據(jù), 用于在將數(shù)據(jù)發(fā)送到服務(wù)器后, 將數(shù)據(jù)進(jìn)行還原
// 如果服務(wù)器響應(yīng)失敗或沒(méi)有返回?cái)?shù)據(jù), 則可以保持修改前的狀態(tài)
current = _.clone(this.attributes);
}
// silentOptions在options對(duì)象中加入了silent(不對(duì)數(shù)據(jù)進(jìn)行驗(yàn)證)
// 當(dāng)使用wait參數(shù)時(shí)使用silentOptions配置項(xiàng), 因?yàn)樵谏厦嬉呀?jīng)對(duì)數(shù)據(jù)進(jìn)行過(guò)驗(yàn)證
// 如果沒(méi)有設(shè)置wait參數(shù), 則仍然使用原始的options配置項(xiàng)
var silentOptions = _.extend({}, options, {
silent : true
});
// 將修改過(guò)最新的數(shù)據(jù)保存到模型中, 便于在sync方法中獲取模型數(shù)據(jù)保存到服務(wù)器
if(attrs && !this.set(attrs, options.wait ? silentOptions : options)) {
return false;
}
var model = this;
// 在options中可以指定保存數(shù)據(jù)成功后的自定義回調(diào)函數(shù)
var success = options.success;
// 服務(wù)器響應(yīng)成功后執(zhí)行success
options.success = function(resp, status, xhr) {
// 獲取服務(wù)器響應(yīng)最新?tīng)顟B(tài)的數(shù)據(jù)
var serverAttrs = model.parse(resp, xhr);
// 如果使用了wait參數(shù), 則優(yōu)先將修改后的數(shù)據(jù)狀態(tài)直接設(shè)置到模型
if(options.wait) {
delete options.wait;
serverAttrs = _.extend(attrs || {}, serverAttrs);
}
// 將最新的數(shù)據(jù)狀態(tài)設(shè)置到模型中
// 如果調(diào)用set方法時(shí)驗(yàn)證失敗, 則不會(huì)調(diào)用自定義的success回調(diào)函數(shù)
if(!model.set(serverAttrs, options))
return false;
if(success) {
// 調(diào)用響應(yīng)成功后自定義的success回調(diào)函數(shù)
success(model, resp);
} else {
// 如果沒(méi)有指定自定義回調(diào), 則默認(rèn)觸發(fā)sync事件
model.trigger('sync', model, resp, options);
}
};
// 請(qǐng)求發(fā)生錯(cuò)誤時(shí)通過(guò)wrapError處理error事件
options.error = Backbone.wrapError(options.error, model, options);
// 將模型中的數(shù)據(jù)保存到服務(wù)器
// 如果當(dāng)前模型是一個(gè)新建的模型(沒(méi)有id), 則使用create方法(新增), 否則認(rèn)為是update方法(修改)
var method = this.isNew() ? 'create' : 'update';
var xhr = (this.sync || Backbone.sync).call(this, method, this, options);
// 如果設(shè)置了options.wait, 則將數(shù)據(jù)還原為修改前的狀態(tài)
// 此時(shí)保存的請(qǐng)求還沒(méi)有得到響應(yīng), 因此如果響應(yīng)失敗, 模型中將保持修改前的狀態(tài), 如果服務(wù)器響應(yīng)成功, 則會(huì)在success中設(shè)置模型中的數(shù)據(jù)為最新?tīng)顟B(tài)
if(options.wait)
this.set(current, silentOptions);
return xhr;
},
// 刪除模型, 模型將同時(shí)從所屬的Collection集合中被刪除
// 如果模型是在客戶端新建的, 則直接從客戶端刪除
// 如果模型數(shù)據(jù)同時(shí)存在服務(wù)器, 則同時(shí)會(huì)刪除服務(wù)器端的數(shù)據(jù)
destroy : function(options) {
// 配置項(xiàng)必須是一個(gè)新的對(duì)象
options = options ? _.clone(options) : {};
var model = this;
// 在options中可以指定刪除數(shù)據(jù)成功后的自定義回調(diào)函數(shù)
var success = options.success;
// 刪除數(shù)據(jù)成功調(diào)用, 觸發(fā)destroy事件, 如果模型存在于Collection集合中, 集合將監(jiān)聽(tīng)destroy事件并在觸發(fā)時(shí)從集合中移除該模型
// 刪除模型時(shí), 模型中的數(shù)據(jù)并沒(méi)有被清空, 但模型已經(jīng)從集合中移除, 因此當(dāng)沒(méi)有任何地方引用該模型時(shí), 會(huì)被自動(dòng)從內(nèi)存中釋放
// 建議在刪除模型時(shí), 將模型對(duì)象的引用變量設(shè)置為null
var triggerDestroy = function() {
model.trigger('destroy', model, model.collection, options);
};
// 如果該模型是一個(gè)客戶端新建的模型, 則直接調(diào)用triggerDestroy從集合中將模型移除
if(this.isNew()) {
triggerDestroy();
return false;
}
// 當(dāng)從服務(wù)器刪除數(shù)據(jù)成功時(shí)
options.success = function(resp) {
// 如果在options對(duì)象中配置wait項(xiàng), 則表示本地內(nèi)存中的模型數(shù)據(jù), 會(huì)在服務(wù)器數(shù)據(jù)被刪除成功后再刪除
// 如果服務(wù)器響應(yīng)失敗, 則本地?cái)?shù)據(jù)不會(huì)被刪除
if(options.wait)
triggerDestroy();
if(success) {
// 調(diào)用自定義的成功回調(diào)函數(shù)
success(model, resp);
} else {
// 如果沒(méi)有自定義回調(diào), 則默認(rèn)觸發(fā)sync事件
model.trigger('sync', model, resp, options);
}
};
// 請(qǐng)求發(fā)生錯(cuò)誤時(shí)通過(guò)wrapError處理error事件
options.error = Backbone.wrapError(options.error, model, options);
// 通過(guò)sync方法發(fā)送刪除數(shù)據(jù)的請(qǐng)求
var xhr = (this.sync || Backbone.sync).call(this, 'delete', this, options);
// 如果沒(méi)有在options對(duì)象中配置wait項(xiàng), 則會(huì)先刪除本地?cái)?shù)據(jù), 再發(fā)送請(qǐng)求刪除服務(wù)器數(shù)據(jù)
// 此時(shí)無(wú)論服務(wù)器刪除是否成功, 本地模型數(shù)據(jù)已被刪除
if(!options.wait)
triggerDestroy();
return xhr;
},
// 獲取模型在服務(wù)器接口中對(duì)應(yīng)的url, 在調(diào)用save, fetch, destroy等與服務(wù)器交互的方法時(shí), 將使用該方法獲取url
// 生成的url類似于"PATHINFO"模式, 服務(wù)器對(duì)模型的操作只有一個(gè)url, 對(duì)于修改和刪除操作會(huì)在url后追加模型id便于標(biāo)識(shí)
// 如果在模型中定義了urlRoot, 服務(wù)器接口應(yīng)為[urlRoot/id]形式
// 如果模型所屬的Collection集合定義了url方法或?qū)傩? 則使用集合中的url形式: [collection.url/id]
// 在訪問(wèn)服務(wù)器url時(shí)會(huì)在url后面追加上模型的id, 便于服務(wù)器標(biāo)識(shí)一條記錄, 因此模型中的id需要與服務(wù)器記錄對(duì)應(yīng)
// 如果無(wú)法獲取模型或集合的url, 將調(diào)用urlError方法拋出一個(gè)異常
// 如果服務(wù)器接口并沒(méi)有按照"PATHINFO"方式進(jìn)行組織, 可以通過(guò)重載url方法實(shí)現(xiàn)與服務(wù)器的無(wú)縫交互
url : function() {
// 定義服務(wù)器對(duì)應(yīng)的url路徑
var base = getValue(this, 'urlRoot') || getValue(this.collection, 'url') || urlError();
// 如果當(dāng)前模型是客戶端新建的模型, 則不存在id屬性, 服務(wù)器url直接使用base
if(this.isNew())
return base;
// 如果當(dāng)前模型具有id屬性, 可能是調(diào)用了save或destroy方法, 將在base后面追加模型的id
// 下面將判斷base最后一個(gè)字符是否是"/", 生成的url格式為[base/id]
return base + (base.charAt(base.length - 1) == '/' ? '' : '/') + encodeURIComponent(this.id);
},
// parse方法用于解析從服務(wù)器獲取的數(shù)據(jù), 返回一個(gè)能夠被set方法解析的模型數(shù)據(jù)
// 一般parse方法會(huì)根據(jù)服務(wù)器返回的數(shù)據(jù)進(jìn)行重載, 以便構(gòu)建與服務(wù)器的無(wú)縫連接
// 當(dāng)服務(wù)器返回的數(shù)據(jù)結(jié)構(gòu)與set方法所需的數(shù)據(jù)結(jié)構(gòu)不一致(例如服務(wù)器返回XML格式數(shù)據(jù)時(shí)), 可使用parse方法進(jìn)行轉(zhuǎn)換
parse : function(resp, xhr) {
return resp;
},
// 創(chuàng)建一個(gè)新的模型, 它具有和當(dāng)前模型相同的數(shù)據(jù)
clone : function() {
return new this.constructor(this.attributes);
},
// 檢查當(dāng)前模型是否是客戶端創(chuàng)建的新模型
// 檢查方式是根據(jù)模型是否存在id標(biāo)識(shí), 客戶端創(chuàng)建的新模型沒(méi)有id標(biāo)識(shí)
// 因此服務(wù)器響應(yīng)的模型數(shù)據(jù)中必須包含id標(biāo)識(shí), 標(biāo)識(shí)的屬性名默認(rèn)為"id", 也可以通過(guò)修改idAttribute屬性自定義標(biāo)識(shí)
isNew : function() {
return this.id == null;
},
// 數(shù)據(jù)被更新時(shí)觸發(fā)change事件綁定的函數(shù)
// 當(dāng)set方法被調(diào)用, 會(huì)自動(dòng)調(diào)用change方法, 如果在set方法被調(diào)用時(shí)指定了silent配置, 則需要手動(dòng)調(diào)用change方法
change : function(options) {
// options必須是一個(gè)對(duì)象
options || ( options = {});
// this._changing相關(guān)的邏輯有些問(wèn)題
// this._changing在方法最后被設(shè)置為false, 因此方法上面changing變量的值始終為false(第一次為undefined)
// 作者的初衷應(yīng)該是想用該變量標(biāo)示change方法是否執(zhí)行完畢, 對(duì)于瀏覽器端單線程的腳本來(lái)說(shuō)沒(méi)有意義, 因?yàn)樵摲椒ū粓?zhí)行時(shí)會(huì)阻塞其它腳本
// changing獲取上一次執(zhí)行的狀態(tài), 如果上一次腳本沒(méi)有執(zhí)行完畢, 則值為true
var changing = this._changing;
// 開(kāi)始執(zhí)行標(biāo)識(shí), 執(zhí)行過(guò)程中值始終為true, 執(zhí)行完畢后this._changing被修改為false
this._changing = true;
// 將非本次改變的數(shù)據(jù)狀態(tài)添加到_pending對(duì)象中
for(var attr in this._silent)
this._pending[attr] = true;
// changes對(duì)象包含了當(dāng)前數(shù)據(jù)上一次執(zhí)行change事件至今, 已被改變的所有數(shù)據(jù)
// 如果之前使用silent未觸發(fā)change事件, 則本次會(huì)被放到changes對(duì)象中
var changes = _.extend({}, options.changes, this._silent);
// 重置_silent對(duì)象
this._silent = {};
// 遍歷changes對(duì)象, 分別針對(duì)每一個(gè)屬性觸發(fā)單獨(dú)的change事件
for(var attr in changes) {
// 將Model對(duì)象, 屬性值, 配置項(xiàng)作為參數(shù)以此傳遞給事件的監(jiān)聽(tīng)函數(shù)
this.trigger('change:' + attr, this, this.get(attr), options);
}
// 如果方法處于執(zhí)行中, 則停止執(zhí)行
if(changing)
return this;
// 觸發(fā)change事件, 任意數(shù)據(jù)被改變后, 都會(huì)依次觸發(fā)"change:屬性"事件和"change"事件
while(!_.isEmpty(this._pending)) {
this._pending = {};
// 觸發(fā)change事件, 并將Model實(shí)例和配置項(xiàng)作為參數(shù)傳遞給監(jiān)聽(tīng)函數(shù)
this.trigger('change', this, options);
// 遍歷changed對(duì)象中的數(shù)據(jù), 并依次將已改變數(shù)據(jù)的狀態(tài)從changed中移除
// 在此之后如果調(diào)用hasChanged檢查數(shù)據(jù)狀態(tài), 將得到false(未改變)
for(var attr in this.changed) {
if(this._pending[attr] || this._silent[attr])
continue;
// 移除changed中數(shù)據(jù)的狀態(tài)
delete this.changed[attr];
}
// change事件執(zhí)行完畢, _previousAttributes屬性將記錄當(dāng)前模型最新的數(shù)據(jù)副本
// 因此如果需要獲取數(shù)據(jù)的上一個(gè)狀態(tài), 一般只通過(guò)在觸發(fā)的change事件中通過(guò)previous或previousAttributes方法獲取
this._previousAttributes = _.clone(this.attributes);
}
// 執(zhí)行完畢標(biāo)識(shí)
this._changing = false;
return this;
},
// 檢查某個(gè)數(shù)據(jù)是否在上一次執(zhí)行change事件后被改變過(guò)
/**
* 一般在change事件中配合previous或previousAttributes方法使用, 如:
* if(model.hasChanged('attr')) {
* var attrPrev = model.previous('attr');
* }
*/
hasChanged : function(attr) {
if(!arguments.length)
return !_.isEmpty(this.changed);
return _.has(this.changed, attr);
},
// 獲取當(dāng)前模型中的數(shù)據(jù)與上一次數(shù)據(jù)中已經(jīng)發(fā)生變化的數(shù)據(jù)集合
// (一般在使用silent屬性時(shí)沒(méi)有調(diào)用change方法, 因此數(shù)據(jù)會(huì)被臨時(shí)抱存在changed屬性中, 上一次的數(shù)據(jù)可通過(guò)previousAttributes方法獲取)
// 如果傳遞了diff集合, 將使用上一次模型數(shù)據(jù)與diff集合中的數(shù)據(jù)進(jìn)行比較, 返回不一致的數(shù)據(jù)集合
// 如果比較結(jié)果中沒(méi)有差異, 則返回false
changedAttributes : function(diff) {
// 如果沒(méi)有指定diff, 將返回當(dāng)前模型較上一次狀態(tài)已改變的數(shù)據(jù)集合, 這些數(shù)據(jù)已經(jīng)被存在changed屬性中, 因此返回changed集合的一個(gè)副本
if(!diff)
return this.hasChanged() ? _.clone(this.changed) : false;
// 指定了需要進(jìn)行比較的diff集合, 將返回上一次的數(shù)據(jù)與diff集合的比較結(jié)果
// old變量存儲(chǔ)了上一個(gè)狀態(tài)的模型數(shù)據(jù)
var val, changed = false, old = this._previousAttributes;
// 遍歷diff集合, 并將每一項(xiàng)與上一個(gè)狀態(tài)的集合進(jìn)行比較
for(var attr in diff) {
// 將比較結(jié)果不一致的數(shù)據(jù)臨時(shí)存儲(chǔ)到changed變量
if(_.isEqual(old[attr], ( val = diff[attr])))
continue;
(changed || (changed = {}))[attr] = val;
}
// 返回比較結(jié)果
return changed;
},
// 在模型觸發(fā)的change事件中, 獲取某個(gè)屬性被改變前上一個(gè)狀態(tài)的數(shù)據(jù), 一般用于進(jìn)行數(shù)據(jù)比較或回滾
// 該方法一般在change事件中調(diào)用, change事件被觸發(fā)后, _previousAttributes屬性存放最新的數(shù)據(jù)
previous : function(attr) {
// attr指定需要獲取上一個(gè)狀態(tài)的屬性名稱
if(!arguments.length || !this._previousAttributes)
return null;
return this._previousAttributes[attr];
},
// 在模型觸發(fā)change事件中, 獲取所有屬性上一個(gè)狀態(tài)的數(shù)據(jù)集合
// 該方法類似于previous()方法, 一般在change事件中調(diào)用, 用于數(shù)據(jù)比較或回滾
previousAttributes : function() {
// 將上一個(gè)狀態(tài)的數(shù)據(jù)對(duì)象克隆為一個(gè)新對(duì)象并返回
return _.clone(this._previousAttributes);
},
// Check if the model is currently in a valid state. It's only possible to
// get into an *invalid* state if you're using silent changes.
// 驗(yàn)證當(dāng)前模型中的數(shù)據(jù)是否能通過(guò)validate方法驗(yàn)證, 調(diào)用前請(qǐng)確保定義了validate方法
isValid : function() {
return !this.validate(this.attributes);
},
// 數(shù)據(jù)驗(yàn)證方法, 在調(diào)用set, save, add等數(shù)據(jù)更新方法時(shí), 被自動(dòng)執(zhí)行
// 驗(yàn)證失敗會(huì)觸發(fā)模型對(duì)象的"error"事件, 如果在options中指定了error處理函數(shù), 則只會(huì)執(zhí)行options.error函數(shù)
// @param {Object} attrs 數(shù)據(jù)模型的attributes屬性, 存儲(chǔ)模型的對(duì)象化數(shù)據(jù)
// @param {Object} options 配置項(xiàng)
// @return {Boolean} 驗(yàn)證通過(guò)返回true, 不通過(guò)返回false
_validate : function(attrs, options) {
// 如果在調(diào)用set, save, add等數(shù)據(jù)更新方法時(shí)設(shè)置了options.silent屬性, 則忽略驗(yàn)證
// 如果Model中沒(méi)有添加validate方法, 則忽略驗(yàn)證
if(options.silent || !this.validate)
return true;
// 獲取對(duì)象中所有的屬性值, 并放入validate方法中進(jìn)行驗(yàn)證
// validate方法包含2個(gè)參數(shù), 分別為模型中的數(shù)據(jù)集合與配置對(duì)象, 如果驗(yàn)證通過(guò)則不返回任何數(shù)據(jù)(默認(rèn)為undefined), 驗(yàn)證失敗則返回帶有錯(cuò)誤信息數(shù)據(jù)
attrs = _.extend({}, this.attributes, attrs);
var error = this.validate(attrs, options);
// 驗(yàn)證通過(guò)
if(!error)
return true;
// 驗(yàn)證未通過(guò)
// 如果配置對(duì)象中設(shè)置了error錯(cuò)誤處理方法, 則調(diào)用該方法并將錯(cuò)誤數(shù)據(jù)和配置對(duì)象傳遞給該方法
if(options && options.error) {
options.error(this, error, options);
} else {
// 如果對(duì)模型綁定了error事件監(jiān)聽(tīng), 則觸發(fā)綁定事件
this.trigger('error', this, error, options);
}
// 返回驗(yàn)證未通過(guò)標(biāo)識(shí)
return false;
}
});
// Backbone.Collection 數(shù)據(jù)模型集合相關(guān)
// -------------------
// Collection集合存儲(chǔ)一系列相同類的數(shù)據(jù)模型, 并提供相關(guān)方法對(duì)模型進(jìn)行操作
var Collection = Backbone.Collection = function(models, options) {
// 配置對(duì)象
options || ( options = {});
// 在配置參數(shù)中設(shè)置集合的模型類
if(options.model)
this.model = options.model;
// 如果設(shè)置了comparator屬性, 則集合中的數(shù)據(jù)將按照comparator方法中的排序算法進(jìn)行排序(在add方法中會(huì)自動(dòng)調(diào)用)
if(options.comparator)
this.comparator = options.comparator;
// 實(shí)例化時(shí)重置集合的內(nèi)部狀態(tài)(第一次調(diào)用時(shí)可理解為定義狀態(tài))
this._reset();
// 調(diào)用自定義初始化方法, 如果需要一般會(huì)重載initialize方法
this.initialize.apply(this, arguments);
// 如果指定了models數(shù)據(jù), 則調(diào)用reset方法將數(shù)據(jù)添加到集合中
// 首次調(diào)用時(shí)設(shè)置了silent參數(shù), 因此不會(huì)觸發(fā)"reset"事件
if(models)
this.reset(models, {
silent : true,
parse : options.parse
});
};
// 通過(guò)extend方法定義集合類原型方法
_.extend(Collection.prototype, Events, {
// 定義集合的模型類, 模型類必須是一個(gè)Backbone.Model的子類
// 在使用集合相關(guān)方法(如add, create等)時(shí), 允許傳入數(shù)據(jù)對(duì)象, 集合方法會(huì)根據(jù)定義的模型類自動(dòng)創(chuàng)建對(duì)應(yīng)的實(shí)例
// 集合中存儲(chǔ)的數(shù)據(jù)模型應(yīng)該都是同一個(gè)模型類的實(shí)例
model : Model,
// 初始化方法, 該方法在集合實(shí)例被創(chuàng)建后自動(dòng)調(diào)用
// 一般會(huì)在定義集合類時(shí)重載該方法
initialize : function() {
},
// 返回一個(gè)數(shù)組, 包含了集合中每個(gè)模型的數(shù)據(jù)對(duì)象
toJSON : function(options) {
// 通過(guò)Undersocre的map方法將集合中每一個(gè)模型的toJSON結(jié)果組成一個(gè)數(shù)組, 并返回
return this.map(function(model) {
// 依次調(diào)用每個(gè)模型對(duì)象的toJSON方法, 該方法默認(rèn)將返回模型的數(shù)據(jù)對(duì)象(復(fù)制的副本)
// 如果需要返回字符串等其它形式, 可以重載toJSON方法
return model.toJSON(options);
});
},
// 向集合中添加一個(gè)或多個(gè)模型對(duì)象
// 默認(rèn)會(huì)觸發(fā)"add"事件, 如果在options中設(shè)置了silent屬性, 可以關(guān)閉此次事件觸發(fā)
// 傳入的models可以是一個(gè)或一系列的模型對(duì)象(Model類的實(shí)例), 如果在集合中設(shè)置了model屬性, 則允許直接傳入數(shù)據(jù)對(duì)象(如 {name: 'test'}), 將自動(dòng)將數(shù)據(jù)對(duì)象實(shí)例化為model指向的模型對(duì)象
add : function(models, options) {
// 局部變量定義
var i, index, length, model, cid, id, cids = {}, ids = {}, dups = [];
options || ( options = {});
// models必須是一個(gè)數(shù)組, 如果只傳入了一個(gè)模型, 則將其轉(zhuǎn)換為數(shù)組
models = _.isArray(models) ? models.slice() : [models];
// 遍歷需要添加的模型列表, 遍歷過(guò)程中, 將執(zhí)行以下操作:
// - 將數(shù)據(jù)對(duì)象轉(zhuǎn)化模型對(duì)象
// - 建立模型與集合之間的引用
// - 記錄無(wú)效和重復(fù)的模型, 并在后面進(jìn)行過(guò)濾
for( i = 0, length = models.length; i < length; i++) {
// 將數(shù)據(jù)對(duì)象轉(zhuǎn)換為模型對(duì)象, 簡(jiǎn)歷模型與集合的引用, 并存儲(chǔ)到model(同時(shí)models中對(duì)應(yīng)的模型已經(jīng)被替換為模型對(duì)象)
if(!( model = models[i] = this._prepareModel(models[i], options))) {
throw new Error("Can't add an invalid model to a collection");
}
// 當(dāng)前模型的cid和id
cid = model.cid;
id = model.id;
// dups數(shù)組中記錄了無(wú)效或重復(fù)的模型索引(models數(shù)組中的索引), 并在下一步進(jìn)行過(guò)濾刪除
// 如果cids, ids變量中已經(jīng)存在了該模型的索引, 則認(rèn)為是同一個(gè)模型在傳入的models數(shù)組中聲明了多次
// 如果_byCid, _byId對(duì)象中已經(jīng)存在了該模型的索引, 則認(rèn)為同一個(gè)模型在當(dāng)前集合中已經(jīng)存在
// 對(duì)于上述兩種情況, 將模型的索引記錄到dups進(jìn)行過(guò)濾刪除
if(cids[cid] || this._byCid[cid] || ((id != null) && (ids[id] || this._byId[id]))) {
dups.push(i);
continue;
}
// 將models中已經(jīng)遍歷過(guò)的模型記錄下來(lái), 用于在下一次循環(huán)時(shí)進(jìn)行重復(fù)檢查
cids[cid] = ids[id] = model;
}
// 從models中刪除無(wú)效或重復(fù)的模型, 保留目前集合中真正需要添加的模型列表
i = dups.length;
while(i--) {
models.splice(dups[i], 1);
}
// 遍歷需要添加的模型, 監(jiān)聽(tīng)模型事件并記錄_byCid, _byId列表, 用于在調(diào)用get和getByCid方法時(shí)作為索引
for( i = 0, length = models.length; i < length; i++) {
// 監(jiān)聽(tīng)模型中的所有事件, 并執(zhí)行_onModelEvent方法
// _onModelEvent方法中會(huì)對(duì)模型拋出的add, remove, destroy和change事件進(jìn)行處理, 以便模型與集合中的狀態(tài)保持同步
( model = models[i]).on('all', this._onModelEvent, this);
// 將模型根據(jù)cid記錄到_byCid對(duì)象, 便于根據(jù)cid進(jìn)行查找
this._byCid[model.cid] = model;
// 將模型根據(jù)id記錄到_byId對(duì)象, 便于根據(jù)id進(jìn)行查找
if(model.id != null)
this._byId[model.id] = model;
}
// 改變集合的length屬性, length屬性記錄了當(dāng)前集合中模型的數(shù)量
this.length += length;
// 設(shè)置新模型列表插入到集合中的位置, 如果在options中設(shè)置了at參數(shù), 則在集合的at位置插入
// 默認(rèn)將插入到集合的末尾
// 如果設(shè)置了comparator自定義排序方法, 則設(shè)置at后還將按照comparator中的方法進(jìn)行排序, 因此最終的順序可能并非在at指定的位置
index = options.at != null ? options.at : this.models.length;
splice.apply(this.models, [index, 0].concat(models));
// 如果設(shè)置了comparator方法, 則將數(shù)據(jù)按照comparator中的算法進(jìn)行排序
// 自動(dòng)排序使用silent屬性阻止觸發(fā)reset事件
if(this.comparator)
this.sort({
silent : true
});
// 依次對(duì)每個(gè)模型對(duì)象觸發(fā)"add"事件, 如果設(shè)置了silent屬性, 則阻止事件觸發(fā)
if(options.silent)
return this;
// 遍歷新增加的模型列表
for( i = 0, length = this.models.length; i < length; i++) {
if(!cids[( model = this.models[i]).cid])
continue;
options.index = i;
// 觸發(fā)模型的"add"事件, 因?yàn)榧媳O(jiān)聽(tīng)了模型的"all"事件, 因此在_onModelEvent方法中, 集合也將觸發(fā)"add"事件
// 詳細(xì)信息可參考Collection.prototype._onModelEvent方法
model.trigger('add', model, this, options);
}
return this;
},
// 從集合中移除模型對(duì)象(支持移除多個(gè)模型)
// 傳入的models可以是需要移除的模型對(duì)象, 或模型的cid和模型的id
// 移除模型并不會(huì)調(diào)用模型的destroy方法
// 如果沒(méi)有設(shè)置options.silent參數(shù), 將觸發(fā)模型的remove事件, 同時(shí)將觸發(fā)集合的remove事件(集合通過(guò)_onModelEvent方法監(jiān)聽(tīng)了模型的所有事件)
remove : function(models, options) {
var i, l, index, model;
// options默認(rèn)為空對(duì)象
options || ( options = {});
// models必須是數(shù)組類型, 當(dāng)只移除一個(gè)模型時(shí), 將其放入一個(gè)數(shù)組
models = _.isArray(models) ? models.slice() : [models];
// 遍歷需要移除的模型列表
for( i = 0, l = models.length; i < l; i++) {
// 所傳入的models列表中可以是需要移除的模型對(duì)象, 或模型的cid和模型的id
// (在getByCid和get方法中, 可通過(guò)cid, id來(lái)獲取模型, 如果傳入的是一個(gè)模型對(duì)象, 則返回模型本身)
model = this.getByCid(models[i]) || this.get(models[i]);
// 沒(méi)有獲取到模型
if(!model)
continue;
// 從_byId列表中移除模型的id引用
delete this._byId[model.id];
// 從_byCid列表中移除模型的cid引用
delete this._byCid[model.cid];
// indexOf是Underscore對(duì)象中的方法, 這里通過(guò)indexOf方法獲取模型在集合中首次出現(xiàn)的位置
index = this.indexOf(model);
// 從集合列表中移除該模型
this.models.splice(index, 1);
// 重置當(dāng)前集合的length屬性(記錄集合中模型的數(shù)量)
this.length--;
// 如果沒(méi)有設(shè)置silent屬性, 則觸發(fā)模型的remove事件
if(!options.silent) {
// 將當(dāng)前模型在集合中的位置添加到options對(duì)象并傳遞給remove監(jiān)聽(tīng)事件, 以便在事件函數(shù)中可以使用
options.index = index;
model.trigger('remove', model, this, options);
}
// 解除模型與集合的關(guān)系, 包括集合中對(duì)模型的引用和事件監(jiān)聽(tīng)
this._removeReference(model);
}
return this;
},
// 向集合的末尾添加模型對(duì)象
// 如果集合類中定義了comparator排序方法, 則通過(guò)push方法添加的模型將按照comparator定義的算法進(jìn)行排序, 因此模型順序可能會(huì)被改變
push : function(model, options) {
// 通過(guò)_prepareModel方法將model實(shí)例化為模型對(duì)象, 這句代碼是多余的, 因?yàn)樵谙旅嬲{(diào)用的add方法中還會(huì)通過(guò)_prepareModel獲取一次模型
model = this._prepareModel(model, options);
// 調(diào)用add方法將模型添加到集合中(默認(rèn)添加到集合末尾)
this.add(model, options);
return model;
},
// 移除集合中最后一個(gè)模型對(duì)象
pop : function(options) {
// 獲取集合中最后一個(gè)模型
var model = this.at(this.length - 1);
// 通過(guò)remove方法移除該模型
this.remove(model, options);
return model;
},
// 向集合的第一個(gè)位置插入模型
// 如果集合類中定義了comparator排序方法, 則通過(guò)unshift方法添加的模型將按照comparator定義的算法進(jìn)行排序, 因此模型順序可能會(huì)被改變
unshift : function(model, options) {
// 通過(guò)_prepareModel方法將model實(shí)例化為模型對(duì)象
model = this._prepareModel(model, options);
// 調(diào)用add方法將模型插入到集合的第一個(gè)位置(設(shè)置at為0)
// 如果定義了comparator排序方法, 集合的順序?qū)⒈恢嘏?
this.add(model, _.extend({
at : 0
}, options));
return model;
},
// 移除并返回集合中的第一個(gè)模型對(duì)象
shift : function(options) {
// 獲得集合中的第一個(gè)模型
var model = this.at(0);
// 從集合中刪除該模型
this.remove(model, options);
// 返回模型對(duì)象
return model;
},
// 根據(jù)id從集合中查找模型并返回
get : function(id) {
if(id == null)
return
void 0;
return this._byId[id.id != null ? id.id : id];
},
// 根據(jù)cid從集合中查找模型并返回
getByCid : function(cid) {
return cid && this._byCid[cid.cid || cid];
},
// 根據(jù)索引(下標(biāo), 從0開(kāi)始)從集合中查找模型并返回
at : function(index) {
return this.models[index];
},
// 對(duì)集合中的模型根據(jù)值進(jìn)行篩選
// attrs是一個(gè)篩選對(duì)象, 如 {name: 'Jack'}, 將返回集合中所有name為"Jack"的模型(數(shù)組)
where : function(attrs) {
// attrs不能為空值
if(_.isEmpty(attrs))
return [];
// 通過(guò)filter方法對(duì)集合中的模型進(jìn)行篩選
// filter方法是Underscore中的方法, 用于將遍歷集合中的元素, 并將能通過(guò)處理器驗(yàn)證(返回值為true)的元素作為數(shù)組返回
return this.filter(function(model) {
// 遍歷attrs對(duì)象中的驗(yàn)證規(guī)則
for(var key in attrs) {
// 將attrs中的驗(yàn)證規(guī)則與集合中的模型進(jìn)行匹配
if(attrs[key] !== model.get(key))
return false;
}
return true;
});
},
// 對(duì)集合中的模型按照comparator屬性指定的方法進(jìn)行排序
// 如果沒(méi)有在options中設(shè)置silent參數(shù), 則排序后將觸發(fā)reset事件
sort : function(options) {
// options默認(rèn)是一個(gè)對(duì)象
options || ( options = {});
// 調(diào)用sort方法必須指定了comparator屬性(排序算法方法), 否則將拋出一個(gè)錯(cuò)誤
if(!this.comparator)
throw new Error('Cannot sort a set without a comparator');
// boundComparator存儲(chǔ)了綁定當(dāng)前集合上下文對(duì)象的comparator排序算法方法
var boundComparator = _.bind(this.comparator, this);
if(this.comparator.length == 1) {
this.models = this.sortBy(boundComparator);
} else {
// 調(diào)用Array.prototype.sort通過(guò)comparator算法對(duì)數(shù)據(jù)進(jìn)行自定義排序
this.models.sort(boundComparator);
}
// 如果沒(méi)有指定silent參數(shù), 則觸發(fā)reset事件
if(!options.silent)
this.trigger('reset', this, options);
return this;
},
// 將集合中所有模型的attr屬性值存放到一個(gè)數(shù)組并返回
pluck : function(attr) {
// map是Underscore中的方法, 用于遍歷一個(gè)集合, 并將所有處理器的返回值作為一個(gè)數(shù)組返回
return _.map(this.models, function(model) {
// 返回當(dāng)前模型的attr屬性值
return model.get(attr);
});
},
// 替換集合中的所有模型數(shù)據(jù)(models)
// 該操作將刪除集合中當(dāng)前的所有數(shù)據(jù)和狀態(tài), 并重新將數(shù)據(jù)設(shè)置為models
// models應(yīng)該是一個(gè)數(shù)組, 可以包含一系列Model模型對(duì)象, 或原始對(duì)象(將在add方法中自動(dòng)創(chuàng)建為模型對(duì)象)
reset : function(models, options) {
// models是進(jìn)行替換的模型(或數(shù)據(jù))數(shù)組
models || ( models = []);
// options默認(rèn)是一個(gè)空對(duì)象
options || ( options = {});
// 遍歷當(dāng)前集合中的模型, 依次刪除并解除它們與集合的引用關(guān)系
for(var i = 0, l = this.models.length; i < l; i++) {
this._removeReference(this.models[i]);
}
// 刪除集合數(shù)據(jù)并重置狀態(tài)
this._reset();
// 通過(guò)add方法將新的模型數(shù)據(jù)添加到集合
// 這里通過(guò)exnted方法將配置項(xiàng)覆蓋到一個(gè)新的對(duì)象, 該對(duì)象默認(rèn)silent為true, 因此不會(huì)觸發(fā)"add"事件
// 如果在調(diào)用reset方法時(shí)沒(méi)有設(shè)置silent屬性則會(huì)觸發(fā)reset事件, 如果設(shè)置為true則不會(huì)觸發(fā)任何事件, 如果設(shè)置為false, 將依次觸發(fā)"add"和"reset"事件
this.add(models, _.extend({
silent : true
}, options));
// 如果在調(diào)用reset方法時(shí)沒(méi)有設(shè)置silent屬性, 則觸發(fā)reset事件
if(!options.silent)
this.trigger('reset', this, options);
return this;
},
// 從服務(wù)器獲取集合的初始化數(shù)據(jù)
// 如果在options中設(shè)置參數(shù)add=true, 則獲取到的數(shù)據(jù)會(huì)被追加到集合中, 否則將以服務(wù)器返回的數(shù)據(jù)替換集合中的當(dāng)前數(shù)據(jù)
fetch : function(options) {
// 復(fù)制options對(duì)象, 因?yàn)閛ptions對(duì)象在后面會(huì)被修改用于臨時(shí)存儲(chǔ)數(shù)據(jù)
options = options ? _.clone(options) : {};
if(options.parse === undefined)
options.parse = true;
// collection記錄當(dāng)前集合對(duì)象, 用于在success回調(diào)函數(shù)中使用
var collection = this;
// 自定義回調(diào)函數(shù), 數(shù)據(jù)請(qǐng)求成功后并添加完成后, 會(huì)調(diào)用自定義success函數(shù)
var success = options.success;
// 當(dāng)從服務(wù)器請(qǐng)求數(shù)據(jù)成功時(shí)執(zhí)行options.success, 該函數(shù)中將解析并添加數(shù)據(jù)
options.success = function(resp, status, xhr) {
// 通過(guò)parse方法對(duì)服務(wù)器返回的數(shù)據(jù)進(jìn)行解析, 如果需要自定義數(shù)據(jù)結(jié)構(gòu), 可以重載parse方法
// 如果在options中設(shè)置add=true, 則調(diào)用add方法將數(shù)據(jù)添加到集合, 否則將通過(guò)reset方法將集合中的數(shù)據(jù)替換為服務(wù)器的返回?cái)?shù)據(jù)
collection[options.add ? 'add' : 'reset'](collection.parse(resp, xhr), options);
// 如果設(shè)置了自定義成功回調(diào), 則執(zhí)行
if(success)
success(collection, resp);
};
// 當(dāng)服務(wù)器返回狀態(tài)錯(cuò)誤時(shí), 通過(guò)wrapError方法處理錯(cuò)誤事件
options.error = Backbone.wrapError(options.error, collection, options);
// 調(diào)用Backbone.sync方法發(fā)送請(qǐng)求從服務(wù)器獲取數(shù)據(jù)
// 如果需要的數(shù)據(jù)并不是從服務(wù)器獲取, 或獲取方式不使用AJAX, 可以重載Backbone.sync方法
return (this.sync || Backbone.sync).call(this, 'read', this, options);
},
// 向集合中添加并創(chuàng)建一個(gè)模型, 同時(shí)將該模型保存到服務(wù)器
// 如果是通過(guò)數(shù)據(jù)對(duì)象來(lái)創(chuàng)建模型, 需要在集合中聲明model屬性對(duì)應(yīng)的模型類
// 如果在options中聲明了wait屬性, 則會(huì)在服務(wù)器創(chuàng)建成功后再將模型添加到集合, 否則先將模型添加到集合, 再保存到服務(wù)器(無(wú)論保存是否成功)
create : function(model, options) {
var coll = this;
// 定義options對(duì)象
options = options ? _.clone(options) : {};
// 通過(guò)_prepareModel獲取模型類的實(shí)例
model = this._prepareModel(model, options);
// 模型創(chuàng)建失敗
if(!model)
return false;
// 如果沒(méi)有聲明wait屬性, 則通過(guò)add方法將模型添加到集合中
if(!options.wait)
coll.add(model, options);
// success存儲(chǔ)保存到服務(wù)器成功之后的自定義回調(diào)函數(shù)(通過(guò)options.success聲明)
var success = options.success;
// 監(jiān)聽(tīng)模型數(shù)據(jù)保存成功后的回調(diào)函數(shù)
options.success = function(nextModel, resp, xhr) {
// 如果聲明了wait屬性, 則在只有在服務(wù)器保存成功后才會(huì)將模型添加到集合中
if(options.wait)
coll.add(nextModel, options);
// 如果聲明了自定義成功回調(diào), 則執(zhí)行自定義函數(shù), 否則將默認(rèn)觸發(fā)模型的sync事件
if(success) {
success(nextModel, resp);
} else {
nextModel.trigger('sync', model, resp, options);
}
};
// 調(diào)用模型的save方法, 將模型數(shù)據(jù)保存到服務(wù)器
model.save(null, options);
return model;
},
// 數(shù)據(jù)解析方法, 用于將服務(wù)器數(shù)據(jù)解析為模型和集合可用的結(jié)構(gòu)化數(shù)據(jù)
// 默認(rèn)將返回resp本身, 這需要與服務(wù)器定義Backbone支持的數(shù)據(jù)格式, 如果需要自定義數(shù)據(jù)格式, 可以重載parse方法
parse : function(resp, xhr) {
return resp;
},
// chain用于構(gòu)建集合數(shù)據(jù)的鏈?zhǔn)讲僮? 它將集合中的數(shù)據(jù)轉(zhuǎn)換為一個(gè)Underscore對(duì)象, 并使用Underscore的chain方法轉(zhuǎn)換為鏈?zhǔn)浇Y(jié)構(gòu)
// 關(guān)于chain方法的轉(zhuǎn)換方式, 可參考Underscore中chain方法的注釋
chain : function() {
return _(this.models).chain();
},
// 刪除所有集合元素并重置集合中的數(shù)據(jù)狀態(tài)
_reset : function(options) {
// 刪除集合元素
this.length = 0;
this.models = [];
// 重置集合狀態(tài)
this._byId = {};
this._byCid = {};
},
// 將模型添加到集合中之前的一些準(zhǔn)備工作
// 包括將數(shù)據(jù)實(shí)例化為一個(gè)模型對(duì)象, 和將集合引用到模型的collection屬性
_prepareModel : function(model, options) {
options || ( options = {});
// 檢查model是否是一個(gè)模型對(duì)象(即Model類的實(shí)例)
if(!( model instanceof Model)) {
// 傳入的model是模型數(shù)據(jù)對(duì)象, 而并非模型對(duì)象
// 將數(shù)據(jù)作為參數(shù)傳遞給Model, 以創(chuàng)建一個(gè)新的模型對(duì)象
var attrs = model;
// 設(shè)置模型引用的集合
options.collection = this;
// 將數(shù)據(jù)轉(zhuǎn)化為模型
model = new this.model(attrs, options);
// 對(duì)模型中的數(shù)據(jù)進(jìn)行驗(yàn)證
if(!model._validate(model.attributes, options))
model = false;
} else if(!model.collection) {
// 如果傳入的是一個(gè)模型對(duì)象但沒(méi)有建立與集合的引用, 則設(shè)置模型的collection屬性為當(dāng)前集合
model.collection = this;
}
return model;
},
// 解綁某個(gè)模型與集合的關(guān)系, 包括對(duì)集合的引用和事件監(jiān)聽(tīng)
// 一般在調(diào)用remove方法刪除模型或調(diào)用reset方法重置狀態(tài)時(shí)自動(dòng)調(diào)用
_removeReference : function(model) {
// 如果模型引用了當(dāng)前集合, 則移除該引用(必須確保所有對(duì)模型的引用已經(jīng)解除, 否則模型可能無(wú)法從內(nèi)存中釋放)
if(this == model.collection) {
delete model.collection;
}
// 取消集合中監(jiān)聽(tīng)的所有模型事件
model.off('all', this._onModelEvent, this);
},
// 在向集合中添加模型時(shí)被自動(dòng)調(diào)用
// 用于監(jiān)聽(tīng)集合中模型的事件, 當(dāng)模型在觸發(fā)事件(add, remove, destroy, change事件)時(shí)集合進(jìn)行相關(guān)處理
_onModelEvent : function(event, model, collection, options) {
// 添加和移除模型的事件, 必須確保模型所屬的集合為當(dāng)前集合對(duì)象
if((event == 'add' || event == 'remove') && collection != this)
return;
// 模型觸發(fā)銷(xiāo)毀事件時(shí), 從集合中移除
if(event == 'destroy') {
this.remove(model, options);
}
// 當(dāng)模型的id被修改時(shí), 集合修改_byId中存儲(chǔ)對(duì)模型的引用, 保持與模型id的同步, 便于使用get()方法獲取模型對(duì)象
if(model && event === 'change:' + model.idAttribute) {
// 獲取模型在改變之前的id, 并根據(jù)此id從集合的_byId列表中移除
delete this._byId[model.previous(model.idAttribute)];
// 以模型新的id作為key, 在_byId列表中存放對(duì)模型的引用
this._byId[model.id] = model;
}
// 在集合中觸發(fā)模型對(duì)應(yīng)的事件, 無(wú)論模型觸發(fā)任何事件, 集合都會(huì)觸發(fā)對(duì)應(yīng)的事件
// (例如當(dāng)模型被添加到集合中時(shí), 會(huì)觸發(fā)模型的"add"事件, 同時(shí)也會(huì)在此方法中觸發(fā)集合的"add"事件)
// 這對(duì)于監(jiān)聽(tīng)并處理集合中模型狀態(tài)的變化非常有效
// 在監(jiān)聽(tīng)的集合事件中, 觸發(fā)對(duì)應(yīng)事件的模型會(huì)被作為參數(shù)傳遞給集合的監(jiān)聽(tīng)函數(shù)
this.trigger.apply(this, arguments);
}
});
// 定義Underscore中的集合操作的相關(guān)方法
// 將Underscore中一系列集合操作方法復(fù)制到Collection集合類的原型對(duì)象中
// 這樣就可以直接通過(guò)集合對(duì)象調(diào)用Underscore相關(guān)的集合方法
// 這些方法在調(diào)用時(shí)所操作的集合數(shù)據(jù)是當(dāng)前Collection對(duì)象的models數(shù)據(jù)
var methods = ['forEach', 'each', 'map', 'reduce', 'reduceRight', 'find', 'detect', 'filter', 'select', 'reject', 'every', 'all', 'some', 'any', 'include', 'contains', 'invoke', 'max', 'min', 'sortBy', 'sortedIndex', 'toArray', 'size', 'first', 'initial', 'rest', 'last', 'without', 'indexOf', 'shuffle', 'lastIndexOf', 'isEmpty', 'groupBy'];
// 遍歷已經(jīng)定義的方法列表
_.each(methods, function(method) {
// 將方法復(fù)制到Collection集合類的原型對(duì)象
Collection.prototype[method] = function() {
// 調(diào)用時(shí)直接使用Underscore的方法, 上下文對(duì)象保持為Underscore對(duì)象
// 需要注意的是這里傳遞給Underscore方法的集合參數(shù)是 this.models, 因此在使用這些方法時(shí), 所操作的集合對(duì)象是當(dāng)前Collection對(duì)象的models數(shù)據(jù)
return _[method].apply(_, [this.models].concat(_.toArray(arguments)));
};
});
// Backbone.Router URL路由器
// -------------------
// 通過(guò)繼承Backbone.Router類實(shí)現(xiàn)自定義的路由器
// 路由器允許定義路由規(guī)則, 通過(guò)URL片段進(jìn)行導(dǎo)航, 并將每一個(gè)規(guī)則對(duì)應(yīng)到一個(gè)方法, 當(dāng)URL匹配某個(gè)規(guī)則時(shí)會(huì)自動(dòng)執(zhí)行該方法
// 路由器通過(guò)URL進(jìn)行導(dǎo)航, 導(dǎo)航方式分為pushState, Hash, 和監(jiān)聽(tīng)方式(詳細(xì)可參考Backbone.History類)
// 在創(chuàng)建Router實(shí)例時(shí), 通過(guò)options.routes來(lái)設(shè)置某個(gè)路由規(guī)則對(duì)應(yīng)的監(jiān)聽(tīng)方法
// options.routes中的路由規(guī)則按照 {規(guī)則名稱: 方法名稱}進(jìn)行組織, 每一個(gè)路由規(guī)則所對(duì)應(yīng)的方法, 都必須是在Router實(shí)例中的已經(jīng)聲明的方法
// options.routes定義的路由規(guī)則按照先后順序進(jìn)行匹配, 如果當(dāng)前URL能被多個(gè)規(guī)則匹配, 則只會(huì)執(zhí)行第一個(gè)匹配的事件方法
var Router = Backbone.Router = function(options) {
// options默認(rèn)是一個(gè)空對(duì)象
options || ( options = {});
// 如果在options中設(shè)置了routes對(duì)象(路由規(guī)則), 則賦給當(dāng)前實(shí)例的routes屬性
// routes屬性記錄了路由規(guī)則與事件方法的綁定關(guān)系, 當(dāng)URL與某一個(gè)規(guī)則匹配時(shí), 會(huì)自動(dòng)調(diào)用關(guān)聯(lián)的事件方法
if(options.routes)
this.routes = options.routes;
// 解析和綁定路由規(guī)則
this._bindRoutes();
// 調(diào)用自定義的初始化方法
this.initialize.apply(this, arguments);
};
// 定義用于將字符串形式的路由規(guī)則, 轉(zhuǎn)換為可執(zhí)行的正則表達(dá)式規(guī)則時(shí)的查找條件
// (字符串形式的路由規(guī)則, 通過(guò)\w+進(jìn)行匹配, 因此只支持字母數(shù)字和下劃線組成的字符串)
// 匹配一個(gè)URL片段中(以/"斜線"為分隔)的動(dòng)態(tài)路由規(guī)則
// 如: (topic/:id) 匹配 (topic/1228), 監(jiān)聽(tīng)事件function(id) { // id為1228 }
var namedParam = /:\w+/g;
// 匹配整個(gè)URL片段中的動(dòng)態(tài)路由規(guī)則
// 如: (topic*id) 匹配 (url#/topic1228), 監(jiān)聽(tīng)事件function(id) { // id為1228 }
var splatParam = /\*\w+/g;
// 匹配URL片段中的特殊字符, 并在字符前加上轉(zhuǎn)義符, 防止特殊字符在被轉(zhuǎn)換為正則表達(dá)式后變成元字符
// 如: (abc)^[,.] 將被轉(zhuǎn)換為 \(abc\)\^\[\,\.\]
var escapeRegExp = /[-[\]{}()+?.,\\^$|#\s]/g;
// 向Router類的原型對(duì)象中擴(kuò)展屬性和方法
_.extend(Router.prototype, Events, {
// 自定義初始化方法, 在路由器Router實(shí)例化后被自動(dòng)調(diào)用
initialize : function() {
},
// 將一個(gè)路由規(guī)則綁定給一個(gè)監(jiān)聽(tīng)事件, 當(dāng)URL片段匹配該規(guī)則時(shí), 會(huì)自動(dòng)調(diào)用觸發(fā)該事件
route : function(route, name, callback) {
// 創(chuàng)建history實(shí)例, Backbone.history是一個(gè)單例對(duì)象, 只在第一次創(chuàng)建路由器對(duì)象時(shí)被實(shí)例化
Backbone.history || (Backbone.history = new History);
// 檢查route規(guī)則名稱是否為一個(gè)字符串(當(dāng)手動(dòng)調(diào)用route方法創(chuàng)建路由規(guī)則時(shí), 允許傳遞一個(gè)正則表達(dá)式或字符串作為規(guī)則)
// 在構(gòu)造Router實(shí)例時(shí)傳入options.routes中的規(guī)則, 都應(yīng)該是一個(gè)字符串(因?yàn)樵赺bindRoutes方法中將routes配置中的key作為路由規(guī)則)
// 如果傳入的是字符串類型的路由規(guī)則, 通過(guò)_routeToRegExp方法將其轉(zhuǎn)換為一個(gè)正則表達(dá)式, 用于匹配URL片段
if(!_.isRegExp(route))
route = this._routeToRegExp(route);
// 如果沒(méi)有設(shè)置callback(事件方法), 則根據(jù)name從當(dāng)前Router實(shí)例中獲取與name同名的方法
// 這是因?yàn)樵谑謩?dòng)調(diào)用route方法時(shí)可能不會(huì)傳遞callback方法, 但必須傳遞name事件名稱, 并在Router實(shí)例中已經(jīng)定義了該方法
if(!callback)
callback = this[name];
// 調(diào)用history實(shí)例的route方法, 該方法會(huì)將轉(zhuǎn)換后的正則表達(dá)式規(guī)則, 和監(jiān)聽(tīng)事件方法綁定到history.handlers列表中, 以便history進(jìn)行路由和控制
// 當(dāng)history實(shí)例匹配到對(duì)應(yīng)的路由規(guī)則而調(diào)用該事件時(shí), 會(huì)將URL片段作為字符串(即fragment參數(shù))傳遞給該事件方法
// 這里并沒(méi)有直接將監(jiān)聽(tīng)事件傳遞給history的route方法, 而是使用bind方法封裝了另一個(gè)函數(shù), 該函數(shù)的執(zhí)行上下文為當(dāng)前Router對(duì)象
Backbone.history.route(route, _.bind(function(fragment) {
// 調(diào)用_extractParameters方法獲取匹配到的規(guī)則中的參數(shù)
var args = this._extractParameters(route, fragment);
// 調(diào)用callback路由監(jiān)聽(tīng)事件, 并將參數(shù)傳遞給監(jiān)聽(tīng)事件
callback && callback.apply(this, args);
// 觸發(fā)route:name事件, name為調(diào)用route時(shí)傳遞的事件名稱
// 如果對(duì)當(dāng)前Router實(shí)例使用on方法綁定了route:name事件, 則會(huì)收到該事件的觸發(fā)通知
this.trigger.apply(this, ['route:' + name].concat(args));
// 觸發(fā)history實(shí)例中綁定的route事件, 當(dāng)路由器匹配到任何規(guī)則時(shí), 均會(huì)觸發(fā)該事件
Backbone.history.trigger('route', this, name, args);
/**
* 事件綁定如:
* var router = new MyRouter();
* router.on('route:routename', function(param) {
* // 綁定到Router實(shí)例中某個(gè)規(guī)則的事件, 當(dāng)匹配到該規(guī)則時(shí)觸發(fā)
* });
* Backbone.history.on('route', function(router, name, args) {
* // 綁定到history實(shí)例中的事件, 當(dāng)匹配到任何規(guī)則時(shí)觸發(fā)
* });
* Backbone.history.start();
*/
}, this));
return this;
},
// 通過(guò)調(diào)用history.navigate方法, 手動(dòng)設(shè)置跳轉(zhuǎn)到URL
navigate : function(fragment, options) {
// 代理到history實(shí)例的navigate方法
Backbone.history.navigate(fragment, options);
},
// 解析當(dāng)前實(shí)例定義的路由(this.routes)規(guī)則, 并調(diào)用route方法將每一個(gè)規(guī)則綁定到對(duì)應(yīng)的方法
_bindRoutes : function() {
// 如果在創(chuàng)建對(duì)象時(shí)沒(méi)有設(shè)置routes規(guī)則, 則不進(jìn)行解析和綁定
if(!this.routes)
return;
// routes變量以二維數(shù)組的形式存儲(chǔ)倒序排列的路由規(guī)則
// 如[['', 'homepage'], ['controller:name', 'toController']]
var routes = [];
// 遍歷routes配置
for(var route in this.routes) {
// 將路由規(guī)則放入一個(gè)新的數(shù)組, 按照[規(guī)則名稱, 綁定方法]組織
// 將該數(shù)組通過(guò)unshift方法放置到routes頂部, 實(shí)現(xiàn)倒序排列
// 這里將routes中的規(guī)則倒序排列, 在后面調(diào)用route方法時(shí)會(huì)再次調(diào)用unshift將順序倒過(guò)來(lái), 以保證最終的順序是按照routes配置中定義的順序來(lái)執(zhí)行的
// 倒換兩次順序后, 會(huì)重新恢復(fù)最初調(diào)用前的順序, 之所以這樣做, 是因?yàn)橛脩艨梢允謩?dòng)調(diào)用route方法動(dòng)態(tài)添加路由規(guī)則, 而手動(dòng)添加的路由規(guī)則會(huì)被添加到列表的第一個(gè), 因此要在route方法中使用unshift來(lái)插入規(guī)則
// 而構(gòu)造Router實(shí)例時(shí)自動(dòng)添加的規(guī)則, 為了保持定義順序, 因此在此處將定義的規(guī)則倒序排列
routes.unshift([route, this.routes[route]]);
}
// 循環(huán)完畢, 此時(shí)routes中存儲(chǔ)了倒序排列的路由規(guī)則
// 循環(huán)路由規(guī)則, 并依次調(diào)用route方法, 將規(guī)則名稱綁定到具體的事件函數(shù)
for(var i = 0, l = routes.length; i < l; i++) {
// 調(diào)用route方法, 并分別傳遞(規(guī)則名稱, 事件函數(shù)名, 事件函數(shù)對(duì)象)
this.route(routes[i][0], routes[i][1], this[routes[i][1]]);
}
},
// 將字符串形式的路由規(guī)則轉(zhuǎn)換為正則表達(dá)式對(duì)象
// (在route方法中檢查到字符串類型的路由規(guī)則后, 會(huì)自動(dòng)調(diào)用該方法進(jìn)行轉(zhuǎn)換)
_routeToRegExp : function(route) {
// 為字符串中特殊字符添加轉(zhuǎn)義符, 防止特殊字符在被轉(zhuǎn)換為正則表達(dá)式后變成元字符(這些特殊字符包括-[\]{}()+?.,\\^$|#\s)
// 將字符串中以/"斜線"為分隔的動(dòng)態(tài)路由規(guī)則轉(zhuǎn)換為([^\/]+), 在正則中表示以/"斜線"開(kāi)頭的多個(gè)字符
// 將字符串中的*"星號(hào)"動(dòng)態(tài)路由規(guī)則轉(zhuǎn)換為(.*?), 在正則中表示0或多個(gè)任意字符(這里使用了非貪婪模式, 因此你可以使用例如這樣的組合路由規(guī)則: *list/:id, 將匹配 orderlist/123 , 同時(shí)會(huì)將"order"和"123"作為參數(shù)傳遞給事件方法 )
// 請(qǐng)注意namedParam和splatParam替換后的正則表達(dá)式都是用()括號(hào)將匹配的內(nèi)容包含起來(lái), 這是為了方便取出匹配的內(nèi)容作為參數(shù)傳遞給事件方法
// 請(qǐng)注意namedParam和splatParam匹配的字符串 :str, *str中的str字符串是無(wú)意義的, 它們會(huì)在下面替換后被忽略, 但一般寫(xiě)作和監(jiān)聽(tīng)事件方法的參數(shù)同名, 以便進(jìn)行標(biāo)識(shí)
route = route.replace(escapeRegExp, '\\$&').replace(namedParam, '([^\/]+)').replace(splatParam, '(.*?)');
// 將轉(zhuǎn)換后的字符串創(chuàng)建為正則表達(dá)式對(duì)象并返回
// 這個(gè)正則表達(dá)式將根據(jù)route字符串中的規(guī)則, 用于匹配URL片段
return new RegExp('^' + route + '$');
},
// 傳入一個(gè)路由規(guī)則(正則表達(dá)式)和URL片段(字符串)進(jìn)行匹配, 并返回從匹配的字符串中獲取參數(shù)
/**
* 例如路由規(guī)則為 'teams/:type/:id', 對(duì)應(yīng)的正則表達(dá)式會(huì)被轉(zhuǎn)換為/^teams/([^/]+)/([^/]+)$/ , (對(duì)路由規(guī)則轉(zhuǎn)換為正則表達(dá)式的過(guò)程可參考_routeToRegExp方法)
* URL片段為 'teams/35/1228'
* 則通過(guò)exec執(zhí)行后的結(jié)果為 ["teams/35/1228", "35", "1228"]
* 數(shù)組中的一個(gè)元素是URL片段字符串本身, 從第二個(gè)開(kāi)始則依次為路由規(guī)則表達(dá)式中的參數(shù)
*/
_extractParameters : function(route, fragment) {
return route.exec(fragment).slice(1);
}
});
// Backbone.History 路由器管理
// ----------------
// History類提供路由管理相關(guān)操作, 包括監(jiān)聽(tīng)URL的變化, (通過(guò)popstate和onhashchange事件進(jìn)行監(jiān)聽(tīng), 對(duì)于不支持事件的瀏覽器通過(guò)setInterval心跳監(jiān)控)
// 提供路由規(guī)則與當(dāng)前URL的匹配驗(yàn)證, 和觸發(fā)相關(guān)的監(jiān)聽(tīng)事件
// History一般不會(huì)被直接調(diào)用, 在第一次實(shí)例化Router對(duì)象時(shí), 將自動(dòng)創(chuàng)建一個(gè)History的單例(通過(guò)Backbone.history訪問(wèn))
var History = Backbone.History = function() {
// handlers屬性記錄了當(dāng)前所有路由對(duì)象中已經(jīng)設(shè)置的規(guī)則和監(jiān)聽(tīng)列表
// 形式如: [{route: route, callback: callback}], route記錄了正則表達(dá)式規(guī)則, callback記錄了匹配規(guī)則時(shí)的監(jiān)聽(tīng)事件
// 當(dāng)history對(duì)象監(jiān)聽(tīng)到URL發(fā)生變化時(shí), 會(huì)自動(dòng)與handlers中定義的規(guī)則進(jìn)行匹配, 并調(diào)用監(jiān)聽(tīng)事件
this.handlers = [];
// 將checkUrl方法的上下文對(duì)象綁定到history對(duì)象, 因?yàn)閏heckUrl方法被作為popstate和onhashchange事件或setInterval的回調(diào)函數(shù), 在執(zhí)行回調(diào)時(shí), 上下文對(duì)象會(huì)被改變
// checkUrl方法用于在監(jiān)聽(tīng)到URL發(fā)生變化時(shí)檢查并調(diào)用loadUrl方法
_.bindAll(this, 'checkUrl');
};
// 定義用于匹配URL片段中首字符是否為"#"或"/"的正則
var routeStripper = /^[#\/]/;
// 定義用于匹配從userAgent中獲取的字符串是否包含IE瀏覽器的標(biāo)識(shí), 用于判斷當(dāng)前瀏覽器是否為IE
var isExplorer = /msie [\w.]+/;
// 記錄當(dāng)前history單例對(duì)象是否已經(jīng)被初始化過(guò)(調(diào)用start方法)
History.started = false;
// 向History類的原型對(duì)象中添加方法, 這些方法可以通過(guò)History的實(shí)例調(diào)用(即Backbone.history對(duì)象)
_.extend(History.prototype, Events, {
// 當(dāng)用戶使用低版本的IE瀏覽器(不支持onhashchange事件)時(shí), 通過(guò)心跳監(jiān)聽(tīng)路由狀態(tài)的變化
// interval屬性設(shè)置心跳頻率(毫秒), 該頻率如果太低可能會(huì)導(dǎo)致延遲, 如果太高可能會(huì)消耗CPU資源(需要考慮用戶使用低端瀏覽器時(shí)的設(shè)備配置)
interval : 50,
// 獲取location中Hash字符串(錨點(diǎn)#后的片段)
getHash : function(windowOverride) {
// 如果傳入了一個(gè)window對(duì)象, 則從該對(duì)象中獲取, 否則默認(rèn)從當(dāng)前window對(duì)象中獲取
var loc = windowOverride ? windowOverride.location : window.location;
// 將錨點(diǎn)(#)后的字符串提取出來(lái)并返回
var match = loc.href.match(/#(.*)$/);
// 如果沒(méi)有找到匹配的內(nèi)容, 則返回空字符串
return match ? match[1] : '';
},
// 根據(jù)當(dāng)前設(shè)置的路由方式, 處理并返回當(dāng)前URL中的路由片段
getFragment : function(fragment, forcePushState) {
// fragment是通過(guò)getHash或從URL中已經(jīng)提取的待處理路由片段(如 #/id/1288)
if(fragment == null) {// 如果沒(méi)有傳遞fragment, 則根據(jù)當(dāng)前路由方式進(jìn)行提取
if(this._hasPushState || forcePushState) {
// 使用了pushState方式進(jìn)行路由
// fragment記錄當(dāng)前域名后的URL路徑
fragment = window.location.pathname;
// search記錄當(dāng)前頁(yè)面后的參數(shù)內(nèi)容
var search = window.location.search;
// 將路徑和參數(shù)合并在一起, 作為待處理的路由片段
if(search)
fragment += search;
} else {
// 使用了hash方式進(jìn)行路由
// 通過(guò)getHash方法獲取當(dāng)前錨點(diǎn)(#)后的字符串作為路由片段
fragment = this.getHash();
}
}
// 根據(jù)配置項(xiàng)中設(shè)置的root參數(shù), 則從路由片段取出root路徑之后的內(nèi)容
if(!fragment.indexOf(this.options.root))
fragment = fragment.substr(this.options.root.length);
// 如果URL片段首字母為"#"或"/", 則去除該字符
// 返回處理之后的URL片段
return fragment.replace(routeStripper, '');
},
// 初始化History實(shí)例, 該方法只會(huì)被調(diào)用一次, 應(yīng)該在創(chuàng)建并初始化Router對(duì)象之后被自動(dòng)調(diào)用
// 該方法作為整個(gè)路由的調(diào)度器, 它將針對(duì)不同瀏覽器監(jiān)聽(tīng)URL片段的變化, 負(fù)責(zé)驗(yàn)證并通知到監(jiān)聽(tīng)函數(shù)
start : function(options) {
// 如果history對(duì)象已經(jīng)被初始化過(guò), 則拋出錯(cuò)誤
if(History.started)
throw new Error("Backbone.history has already been started");
// 設(shè)置history對(duì)象的初始化狀態(tài)
History.started = true;
// 設(shè)置配置項(xiàng), 使用調(diào)用start方法時(shí)傳遞的options配置項(xiàng)覆蓋默認(rèn)配置
this.options = _.extend({}, {
// root屬性設(shè)置URL導(dǎo)航中的路由根目錄
// 如果使用pushState方式進(jìn)行路由, 則root目錄之后的地址會(huì)根據(jù)不同的路由產(chǎn)生不同的地址(這可能會(huì)定位到不同的頁(yè)面, 因此需要確保服務(wù)器支持)
// 如果使用Hash錨點(diǎn)的方式進(jìn)行路由, 則root表示URL后錨點(diǎn)(#)的位置
root : '/'
}, this.options, options);
/**
* history針對(duì)不同瀏覽器特性, 實(shí)現(xiàn)了3種方式的監(jiān)聽(tīng):
* - 對(duì)于支持HTML5中popstate事件的瀏覽器, 通過(guò)popstate事件進(jìn)行監(jiān)聽(tīng)
* - 對(duì)于不支持popstate的瀏覽器, 使用onhashchange事件進(jìn)行監(jiān)聽(tīng)(通過(guò)改變hash(錨點(diǎn))設(shè)置的URL在被載入時(shí)會(huì)觸發(fā)onhashchange事件)
* - 對(duì)于不支持popstate和onhashchange事件的瀏覽器, 通過(guò)保持心跳監(jiān)聽(tīng)
*
* 關(guān)于HTML5中popstate事件的相關(guān)方法:
* - pushState可以將指定的URL添加一個(gè)新的history實(shí)體到瀏覽器歷史里
* - replaceState方法可以將當(dāng)前的history實(shí)體替換為指定的URL
* 使用pushState和replaceState方法時(shí)僅替換當(dāng)前URL, 而并不會(huì)真正轉(zhuǎn)到這個(gè)URL(當(dāng)使用后退或前進(jìn)按鈕時(shí), 也不會(huì)跳轉(zhuǎn)到該URL)
* (這兩個(gè)方法可以解決在AJAX單頁(yè)應(yīng)用中瀏覽器前進(jìn), 后退操作的問(wèn)題)
* 當(dāng)使用pushState或replaceState方法替換的URL, 在被載入時(shí)會(huì)觸發(fā)onpopstate事件
* 瀏覽器支持情況:
* Chrome 5, Firefox 4.0, IE 10, Opera 11.5, Safari 5.0
*
* 注意:
* - history.start方法默認(rèn)使用Hash方式進(jìn)行導(dǎo)航
* - 如果需要啟用pushState方式進(jìn)行導(dǎo)航, 需要在調(diào)用start方法時(shí), 手動(dòng)傳入配置options.pushState
* (設(shè)置前請(qǐng)確保瀏覽器支持pushState特性, 否則將默認(rèn)轉(zhuǎn)換為Hash方式)
* - 當(dāng)使用pushState方式進(jìn)行導(dǎo)航時(shí), URL可能會(huì)從options.root指定的根目錄后發(fā)生變化, 這可能會(huì)導(dǎo)航到不同頁(yè)面, 因此請(qǐng)確保服務(wù)器已經(jīng)支持pushState方式的導(dǎo)航
*/
// _wantsHashChange屬性記錄是否希望使用hash(錨點(diǎn))的方式來(lái)記錄和導(dǎo)航路由器
// 除非在options配置項(xiàng)中手動(dòng)設(shè)置hashChange為false, 否則默認(rèn)將使用hash錨點(diǎn)的方式
// (如果手動(dòng)設(shè)置了options.pushState為true, 且瀏覽器支持pushState特性, 則會(huì)使用pushState方式)
this._wantsHashChange = this.options.hashChange !== false;
// _wantsPushState屬性記錄是否希望使用pushState方式來(lái)記錄和導(dǎo)航路由器
// pushState是HTML5中為window.history添加的新特性, 如果沒(méi)有手動(dòng)聲明options.pushState為true, 則默認(rèn)將使用hash方式
this._wantsPushState = !!this.options.pushState;
// _hasPushState屬性記錄瀏覽器是否支持pushState特性
// 如果在options中設(shè)置了pushState(即希望使用pushState方式), 則檢查瀏覽器是否支持該特性
this._hasPushState = !!(this.options.pushState && window.history && window.history.pushState);
// 獲取當(dāng)前URL中的路由字符串
var fragment = this.getFragment();
// documentMode是IE瀏覽器的獨(dú)有屬性, 用于標(biāo)識(shí)當(dāng)前瀏覽器使用的渲染模式
var docMode = document.documentMode;
// oldIE用于檢查當(dāng)前瀏覽器是否為低版本的IE瀏覽器(即IE 7.0以下版本)
// 這句代碼可理解為: 當(dāng)前瀏覽器為IE, 但不支持documentMode屬性, 或documentMode屬性返回的渲染模式為IE7.0以下
var oldIE = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7));
if(oldIE) {
// 如果用戶使用低版本的IE瀏覽器, 不支持popstate和onhashchange事件
// 向DOM中插入一個(gè)隱藏的iframe, 并通過(guò)改變和心跳監(jiān)聽(tīng)該iframe的URL實(shí)現(xiàn)路由
this.iframe = $('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo('body')[0].contentWindow;
// 通過(guò)navigate將iframe設(shè)置到當(dāng)前的URL片段, 這并不會(huì)真正加載到一個(gè)頁(yè)面, 因?yàn)閒ragment并非一個(gè)完整的URL
this.navigate(fragment);
}
// 開(kāi)始監(jiān)聽(tīng)路由狀態(tài)變化
if(this._hasPushState) {
// 如果使用了pushState方式路由, 且瀏覽器支持該特性, 則將popstate事件監(jiān)聽(tīng)到checkUrl方法
$(window).bind('popstate', this.checkUrl);
} else if(this._wantsHashChange && ('onhashchange' in window) && !oldIE) {
// 如果使用Hash方式進(jìn)行路由, 且瀏覽器支持onhashchange事件, 則將hashchange事件監(jiān)聽(tīng)到checkUrl方法
$(window).bind('hashchange', this.checkUrl);
} else if(this._wantsHashChange) {
// 對(duì)于低版本的瀏覽器, 通過(guò)setInterval方法心跳監(jiān)聽(tīng)checkUrl方法, interval屬性標(biāo)識(shí)心跳頻率
this._checkUrlInterval = setInterval(this.checkUrl, this.interval);
}
// 記錄當(dāng)前的URL片段
this.fragment = fragment;
// 驗(yàn)證當(dāng)前是否處于根路徑(即options.root中所配置的路徑)
var loc = window.location;
var atRoot = loc.pathname == this.options.root;
// 如果用戶通過(guò)pushState方式的URL訪問(wèn)到當(dāng)前地址, 但用戶此時(shí)所使用的瀏覽器并不支持pushState特性
// (這可能是某個(gè)用戶通過(guò)pushState方式訪問(wèn)該應(yīng)用, 然后將地址分享給其他用戶, 而其他用戶的瀏覽器并不支持該特性)
if(this._wantsHashChange && this._wantsPushState && !this._hasPushState && !atRoot) {
// 獲取當(dāng)前pushState方式中的URL片段, 并通過(guò)Hash方式重新打開(kāi)頁(yè)面
this.fragment = this.getFragment(null, true);
// 例如hashState方式的URL為 /root/topic/12001, 重新打開(kāi)的Hash方式的URL則為 /root#topic/12001
window.location.replace(this.options.root + '#' + this.fragment);
return true;
// 如果用戶通過(guò)Hash方式的URL訪問(wèn)到當(dāng)前地址, 但調(diào)用Backbone.history.start方法時(shí)設(shè)置了pushState(希望通過(guò)pushState方式進(jìn)行路由)
// 且用戶瀏覽器支持pushState特性, 則將當(dāng)前URL替換為pushState方式(注意, 這里使用replaceState方式進(jìn)行替換URL, 而頁(yè)面不會(huì)被刷新)
// 以下分支條件可理解為: 如果我們希望使用pushState方式進(jìn)行路由, 且瀏覽器支持該特性, 同時(shí)用戶還使用了Hash方式打開(kāi)當(dāng)前頁(yè)面
// (這可能是某個(gè)用戶使用Hash方式瀏覽到一個(gè)URL, 并將URL分享給另一個(gè)瀏覽器支持pushState特性的用戶, 當(dāng)該用戶訪問(wèn)時(shí)會(huì)執(zhí)行此分支)
} else if(this._wantsPushState && this._hasPushState && atRoot && loc.hash) {
// 獲取URL中的Hash片段, 并清除字符串首個(gè)"#"或"/"
this.fragment = this.getHash().replace(routeStripper, '');
// 使用replaceState方法將當(dāng)前瀏覽器的URL替換為pushState支持的方式, 即: 協(xié)議//主機(jī)地址/URL路徑/Hash參數(shù), 例如:
// 當(dāng)用戶訪問(wèn)Hash方式的URL為 /root/#topic/12001, 將被替換為 /root/topic/12001
// 注:
// pushState和replaceState方法的參數(shù)有3個(gè), 分別是state, title, url
// -state: 用于存儲(chǔ)插入或修改的history實(shí)體信息
// -title: 用于設(shè)置瀏覽器標(biāo)題(屬于保留參數(shù), 目前瀏覽器還沒(méi)有實(shí)現(xiàn)該特性)
// -url: 設(shè)置history實(shí)體的URL地址(可以是絕對(duì)或相對(duì)路徑, 但無(wú)法設(shè)置跨域URL)
window.history.replaceState({}, document.title, loc.protocol + '//' + loc.host + this.options.root + this.fragment);
}
// 一般調(diào)用start方法時(shí)會(huì)自動(dòng)調(diào)用loadUrl, 匹配當(dāng)前URL片段對(duì)應(yīng)的路由規(guī)則, 調(diào)用該規(guī)則的方法
// 如果設(shè)置了silent屬性為true, 則loadUrl方法不會(huì)被調(diào)用
// 這種情況一般出現(xiàn)在調(diào)用了stop方法重置history對(duì)象狀態(tài)后, 再次調(diào)用start方法啟動(dòng)(實(shí)際上此時(shí)并非為頁(yè)面初始化, 因此會(huì)設(shè)置silent屬性)
if(!this.options.silent) {
return this.loadUrl();
}
},
// 停止history對(duì)路由的監(jiān)控, 并將狀態(tài)恢復(fù)為未監(jiān)聽(tīng)狀態(tài)
// 調(diào)用stop方法之后, 可重新調(diào)用start方法開(kāi)始監(jiān)聽(tīng), stop方法一般用戶在調(diào)用start方法之后, 需要重新設(shè)置start方法的參數(shù), 或用于單元測(cè)試
stop : function() {
// 解除對(duì)瀏覽器路由的onpopstate和onhashchange事件的監(jiān)聽(tīng)
$(window).unbind('popstate', this.checkUrl).unbind('hashchange', this.checkUrl);
// 停止對(duì)于低版本的IE瀏覽器的心跳監(jiān)控
clearInterval(this._checkUrlInterval);
// 恢復(fù)started狀態(tài), 便于下次重新調(diào)用start方法
History.started = false;
},
// 向handlers中綁定一個(gè)路由規(guī)則(參數(shù)route, 類型為正則表達(dá)式)與事件(參數(shù)callback)的映射關(guān)系(該方法由Router的實(shí)例自動(dòng)調(diào)用)
route : function(route, callback) {
// 將route和callback插入到handlers列表的第一個(gè)位置
// 這是為了確保最后調(diào)用route時(shí)傳入的規(guī)則被優(yōu)先進(jìn)行匹配
this.handlers.unshift({
// 路由規(guī)則(正則)
route : route,
// 匹配規(guī)則時(shí)執(zhí)行的方法
callback : callback
});
},
// 檢查當(dāng)前的URL相對(duì)上一次的狀態(tài)是否發(fā)生了變化
// 如果發(fā)生變化, 則記錄新的URL狀態(tài), 并調(diào)用loadUrl方法觸發(fā)新URL與匹配路由規(guī)則的方法
// 該方法在onpopstate和onhashchange事件被觸發(fā)后自動(dòng)調(diào)用, 或者在低版本的IE瀏覽器中由setInterval心跳定時(shí)調(diào)用
checkUrl : function(e) {
// 獲取當(dāng)前的URL片段
var current = this.getFragment();
// 對(duì)低版本的IE瀏覽器, 將從iframe中獲取最新的URL片段并賦給current變量
if(current == this.fragment && this.iframe)
current = this.getFragment(this.getHash(this.iframe));
// 如果當(dāng)前URL與上一次的狀態(tài)沒(méi)有發(fā)生任何變化, 則停止執(zhí)行
if(current == this.fragment)
return false;
// 執(zhí)行到這里, URL已經(jīng)發(fā)生改變, 調(diào)用navigate方法將URL設(shè)置為當(dāng)前URL
// 這里在自動(dòng)調(diào)用navigate方法時(shí), 并沒(méi)有傳遞options參數(shù), 因此不會(huì)觸發(fā)navigate方法中的loadUrl方法
if(this.iframe)
this.navigate(current);
// 調(diào)用loadUrl方法, 檢查匹配的規(guī)則, 并執(zhí)行規(guī)則綁定的方法
// 如果調(diào)用this.loadUrl方法沒(méi)有成功, 則試圖在調(diào)用loadUrl方法時(shí), 將重新獲取的當(dāng)前Hash傳遞給該方法
this.loadUrl() || this.loadUrl(this.getHash());
},
// 根據(jù)當(dāng)前URL, 與handler路由列表中的規(guī)則進(jìn)行匹配
// 如果URL符合某一個(gè)規(guī)則, 則執(zhí)行這個(gè)規(guī)則所對(duì)應(yīng)的方法, 函數(shù)將返回true
// 如果沒(méi)有找到合適的規(guī)則, 將返回false
// loadUrl方法一般在頁(yè)面初始化時(shí)調(diào)用start方法會(huì)被自動(dòng)調(diào)用(除非設(shè)置了silent參數(shù)為true)
// - 或當(dāng)用戶改變URL后, 由checkUrl監(jiān)聽(tīng)到URL發(fā)生變化時(shí)被調(diào)用
// - 或當(dāng)調(diào)用navigate方法手動(dòng)導(dǎo)航到某個(gè)URL時(shí)被調(diào)用
loadUrl : function(fragmentOverride) {
// 獲取當(dāng)前URL片段
var fragment = this.fragment = this.getFragment(fragmentOverride);
// 調(diào)用Undersocre的any方法, 將URL片段與handlers中的所有規(guī)則依次進(jìn)行匹配
var matched = _.any(this.handlers, function(handler) {
// 如果handlers中的規(guī)則與當(dāng)前URL片段匹配, 則執(zhí)行該歸額對(duì)應(yīng)的方法, 并返回true
if(handler.route.test(fragment)) {
handler.callback(fragment);
return true;
}
});
// matched是any方法的返回值, 如果匹配到規(guī)則則返回true, 沒(méi)有匹配到返回false
return matched;
},
// 導(dǎo)航到指定的URL
// 如果在options中設(shè)置了trigger, 將觸發(fā)導(dǎo)航的URL與對(duì)應(yīng)路由規(guī)則的事件
// 如果在options中設(shè)置了replace, 將使用需要導(dǎo)航的URL替換當(dāng)前的URL在history中的位置
navigate : function(fragment, options) {
// 如果沒(méi)有調(diào)用start方法, 或已經(jīng)調(diào)用stop方法, 則無(wú)法導(dǎo)航
if(!History.started)
return false;
// 如果options參數(shù)不是一個(gè)對(duì)象, 而是true值, 則默認(rèn)trigger配置項(xiàng)為true(即觸發(fā)導(dǎo)航的URL與對(duì)應(yīng)路由規(guī)則的事件)
if(!options || options === true)
options = {
trigger : options
};
// 將傳遞的fragment(URL片段)去掉首字符的"#"或"/"
var frag = (fragment || '').replace(routeStripper, '');
// 如果當(dāng)前URL與需要導(dǎo)航的URL沒(méi)有變化, 則不繼續(xù)執(zhí)行
if(this.fragment == frag)
return;
// 如果當(dāng)前支持并使用了pushState方式進(jìn)行導(dǎo)航
if(this._hasPushState) {
// 構(gòu)造一個(gè)完整的URL, 如果當(dāng)前URL片段中沒(méi)有包含根路徑, 則使用根路徑連接URL片段
if(frag.indexOf(this.options.root) != 0)
frag = this.options.root + frag;
// 設(shè)置新的URL
this.fragment = frag;
// 如果在options選項(xiàng)中設(shè)置了replace屬性, 則將新的URL替換到history中的當(dāng)前URL, 否則默認(rèn)將新的URL追加到history中
window.history[options.replace ? 'replaceState' : 'pushState']({}, document.title, frag);
// 如果使用hash方式進(jìn)行導(dǎo)航
} else if(this._wantsHashChange) {
// 設(shè)置新的hash
this.fragment = frag;
// 調(diào)用_updateHash方法更新當(dāng)前URL為新的hash, 并將options中的replace配置傳遞給_updateHash方法(在該方法中實(shí)現(xiàn)替換或追加新的hash)
this._updateHash(window.location, frag, options.replace);
// 對(duì)于低版本的IE瀏覽器, 當(dāng)Hash發(fā)生變化時(shí), 更新iframe URL中的Hash
if(this.iframe && (frag != this.getFragment(this.getHash(this.iframe)))) {
// 如果使用了replace參數(shù)替換當(dāng)前URL, 則直接將iframe替換為新的文檔
// 調(diào)用document.open打開(kāi)一個(gè)新的文檔, 以擦除當(dāng)前文檔中的內(nèi)容(這里調(diào)用close方法是為了關(guān)閉文檔的狀態(tài))
// open和close方法之間沒(méi)有使用write或writeln方法輸出內(nèi)容, 因此這是一個(gè)空文檔
if(!options.replace)
this.iframe.document.open().close();
// 調(diào)用_updateHash方法更新iframe中的URL
this._updateHash(this.iframe.location, frag, options.replace);
}
} else {
// 如果在調(diào)用start方法時(shí), 手動(dòng)設(shè)置hashChange參數(shù)為true, 不希望使用pushState和hash方式導(dǎo)航
// 則直接將頁(yè)面跳轉(zhuǎn)到新的URL
window.location.assign(this.options.root + fragment);
}
// 如果在options配置項(xiàng)中設(shè)置了trigger屬性, 則調(diào)用loadUrl方法查找路由規(guī)則, 并執(zhí)行規(guī)則對(duì)應(yīng)的事件
// 在URL發(fā)生變化時(shí), 通過(guò)checkUrl方法監(jiān)聽(tīng)到的狀態(tài), 會(huì)在checkUrl方法中自動(dòng)調(diào)用loadUrl方法
// 在手動(dòng)調(diào)用navigate方法時(shí), 如果需要觸發(fā)路由事件, 則需要傳遞trigger參數(shù)
if(options.trigger)
this.loadUrl(fragment);
},
// 更新或設(shè)置當(dāng)前URL中的Has串, _updateHash方法在使用hash方式導(dǎo)航時(shí)被自動(dòng)調(diào)用(navigate方法中)
// location是需要更新hash的window.location對(duì)象
// fragment是需要更新的hash串
// 如果需要將新的hash替換到當(dāng)前URL, 可以設(shè)置replace為true
_updateHash : function(location, fragment, replace) {
// 如果設(shè)置了replace為true, 則使用location.replace方法替換當(dāng)前的URL
// 使用replace方法替換URL后, 新的URL將占有原有URL在history歷史中的位置
if(replace) {
// 將當(dāng)前URL與hash組合為一個(gè)完整的URL并替換
location.replace(location.toString().replace(/(javascript:|#).*$/, '') + '#' + fragment);
} else {
// 沒(méi)有使用替換方式, 直接設(shè)置location.hash為新的hash串
location.hash = fragment;
}
}
});
// Backbone.View 視圖相關(guān)
// -------------
// 視圖類用于創(chuàng)建與數(shù)據(jù)低耦合的界面控制對(duì)象, 通過(guò)將視圖的渲染方法綁定到數(shù)據(jù)模型的change事件, 當(dāng)數(shù)據(jù)發(fā)生變化時(shí)會(huì)通知視圖進(jìn)行渲染
// 視圖對(duì)象中的el用于存儲(chǔ)當(dāng)前視圖所需要操作的DOM最父層元素, 這主要是為了提高元素的查找和操作效率, 其優(yōu)點(diǎn)包括:
// - 查找或操作元素時(shí), 將操作的范圍限定在el元素內(nèi), 不需要再整個(gè)文檔樹(shù)中搜索
// - 在為元素綁定事件時(shí), 可以方便地將事件綁定到el元素(默認(rèn)也會(huì)綁定到el元素)或者是其子元素
// - 在設(shè)計(jì)模式中, 將一個(gè)視圖相關(guān)的元素, 事件, 和邏輯限定在該視圖的范圍中, 降低視圖與視圖間的耦合(至少在邏輯上是這樣)
var View = Backbone.View = function(options) {
// 為每一個(gè)視圖對(duì)象創(chuàng)建一個(gè)唯一標(biāo)識(shí), 前綴為"view"
this.cid = _.uniqueId('view');
// 設(shè)置初始化配置
this._configure(options || {});
// 設(shè)置或創(chuàng)建視圖中的元素
this._ensureElement();
// 調(diào)用自定義的初始化方法
this.initialize.apply(this, arguments);
// 解析options中設(shè)置的events事件列表, 并將事件綁定到視圖中的元素
this.delegateEvents();
};
// 定義用于解析events參數(shù)中事件名稱和元素的正則
var delegateEventSplitter = /^(\S+)\s*(.*)$/;
// viewOptions列表記錄一些列屬性名, 在構(gòu)造視圖對(duì)象時(shí), 如果傳遞的配置項(xiàng)中包含這些名稱, 則將屬性復(fù)制到對(duì)象本身
var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName'];
// 向視圖類的原型對(duì)象中添加一些方法
_.extend(View.prototype, Events, {
// 如果在創(chuàng)建視圖對(duì)象時(shí), 沒(méi)有設(shè)置指定的el元素, 則會(huì)通過(guò)make方法創(chuàng)建一個(gè)元素, tagName為創(chuàng)建元素的默認(rèn)標(biāo)簽
// 也可以通過(guò)在options中自定義tagName來(lái)覆蓋默認(rèn)的"div"標(biāo)簽
tagName : 'div',
// 每個(gè)視圖中都具有一個(gè)$選擇器方法, 該方法與jQuery或Zepto類似, 通過(guò)傳遞一個(gè)表達(dá)式來(lái)獲取元素
// 但該方法只會(huì)在視圖對(duì)象的$el元素范圍內(nèi)進(jìn)行查找, 因此會(huì)提高匹配效率
$ : function(selector) {
return this.$el.find(selector);
},
// 初始化方法, 在對(duì)象被實(shí)例化后自動(dòng)調(diào)用
initialize : function() {
},
// render方法與initialize方法類似, 默認(rèn)沒(méi)有實(shí)現(xiàn)任何邏輯
// 一般會(huì)重載該方法, 以實(shí)現(xiàn)對(duì)視圖中元素的渲染
render : function() {
// 返回當(dāng)前視圖對(duì)象, 以支持方法的鏈?zhǔn)讲僮?
// 因此如果重載了該方法, 建議在方法最后也返回視圖對(duì)象(this)
return this;
},
// 移除當(dāng)前視圖的$el元素
remove : function() {
// 通過(guò)調(diào)用jQuery或Zepto的remove方法, 因此在第三方庫(kù)中會(huì)同時(shí)移除該元素綁定的所有事件和數(shù)據(jù)
this.$el.remove();
return this;
},
// 根據(jù)傳入的標(biāo)簽名稱, 屬性和內(nèi)容, 創(chuàng)建并返回一個(gè)DOM元素
// 該方法用于在內(nèi)部創(chuàng)建this.el時(shí)自動(dòng)調(diào)用
make : function(tagName, attributes, content) {
// 根據(jù)tagName創(chuàng)建元素
var el = document.createElement(tagName);
// 設(shè)置元素屬性
if(attributes)
$(el).attr(attributes);
// 設(shè)置元素內(nèi)容
if(content)
$(el).html(content);
// 返回元素
return el;
},
// 為視圖對(duì)象設(shè)置標(biāo)準(zhǔn)的$el及el屬性, 該方法在對(duì)象創(chuàng)建時(shí)被自動(dòng)調(diào)用
// $el是通過(guò)jQuery或Zepto創(chuàng)建的對(duì)象, el是標(biāo)準(zhǔn)的DOM對(duì)象
setElement : function(element, delegate) {
// 如果已經(jīng)存在了$el屬性(可能是手動(dòng)調(diào)用了setElement方法切換視圖的元素), 則取消之前對(duì)$el綁定的events事件(詳細(xì)參考undelegateEvents方法)
if(this.$el)
this.undelegateEvents();
// 將元素創(chuàng)建為jQuery或Zepto對(duì)象, 并存放在$el屬性中
this.$el = ( element instanceof $) ? element : $(element);
// this.el存放標(biāo)準(zhǔn)的DOM對(duì)象
this.el = this.$el[0];
// 如果設(shè)置了delegate參數(shù), 則為元素綁定視圖中events參數(shù)設(shè)置的事件
// 在視圖類的構(gòu)造函數(shù)中, 已經(jīng)調(diào)用了delegateEvents方法進(jìn)行綁定, 因此在初始化的_ensureElement方法中調(diào)用setElement方法時(shí)沒(méi)有傳遞delegate參數(shù)
// 在手動(dòng)調(diào)用setElemen方法設(shè)置視圖元素時(shí), 允許傳遞delegate綁定事件
if(delegate !== false)
this.delegateEvents();
return this;
},
// 為視圖元素綁定事件
// events參數(shù)配置了需要綁定事件的集合, 格式如('事件名稱 元素選擇表達(dá)式' : '事件方法名稱/或事件函數(shù)'):
// {
// 'click #title': 'edit',
// 'click .save': 'save'
// 'click span': function() {}
// }
// 該方法在視圖對(duì)象初始化時(shí)會(huì)被自動(dòng)調(diào)用, 并將對(duì)象中的events屬性作為events參數(shù)(事件集合)
delegateEvents : function(events) {
// 如果沒(méi)有手動(dòng)傳遞events參數(shù), 則從視圖對(duì)象獲取events屬性作為事件集合
if(!(events || ( events = getValue(this, 'events'))))
return;
// 取消當(dāng)前已經(jīng)綁定過(guò)的events事件
this.undelegateEvents();
// 遍歷需要綁定的事件列表
for(var key in events) {
// 獲取需要綁定的方法(允許是方法名稱或函數(shù))
var method = events[key];
// 如果是方法名稱, 則從對(duì)象中獲取該函數(shù)對(duì)象, 因此該方法名稱必須是視圖對(duì)象中已定義的方法
if(!_.isFunction(method))
method = this[events[key]];
// 對(duì)無(wú)效的方法拋出一個(gè)錯(cuò)誤
if(!method)
throw new Error('Method "' + events[key] + '" does not exist');
// 解析事件表達(dá)式(key), 從表達(dá)式中解析出事件的名字和需要操作的元素
// 例如 'click #title'將被解析為 'click' 和 '#title' 兩部分, 均存放在match數(shù)組中
var match = key.match(delegateEventSplitter);
// eventName為解析后的事件名稱
// selector為解析后的事件元素選擇器表達(dá)式
var eventName = match[1], selector = match[2];
// bind方法是Underscore中用于綁定函數(shù)上下文的方法
// 這里將method事件方法的上下文綁定到當(dāng)前視圖對(duì)象, 因此在事件被觸發(fā)后, 事件方法中的this始終指向視圖對(duì)象本身
method = _.bind(method, this);
// 設(shè)置事件名稱, 在事件名稱后追加標(biāo)識(shí), 用于傳遞給jQuery或Zepto的事件綁定方法
eventName += '.delegateEvents' + this.cid;
// 通過(guò)jQuery或Zepto綁定事件
if(selector === '') {
// 如果沒(méi)有設(shè)置子元素選擇器, 則通過(guò)bind方法將事件和方法綁定到當(dāng)前$el元素本身
this.$el.bind(eventName, method);
} else {
// 如果當(dāng)前設(shè)置了子元素選擇器表達(dá)式, 則通過(guò)delegate方式綁定
// 該方法將查找當(dāng)前$el元素下的子元素, 并將于selector表達(dá)式匹配的元素進(jìn)行事件綁定
// 如果該選擇器的元素不屬于當(dāng)前$el的子元素, 則事件綁定無(wú)效
this.$el.delegate(selector, eventName, method);
}
}
},
// 取消視圖中當(dāng)前元素綁定的events事件, 該方法一般不會(huì)被使用
// 除非調(diào)用delegateEvents方法重新為視圖中的元素綁定事件, 在重新綁定之前會(huì)清除當(dāng)前的事件
// 或通過(guò)setElement方法重新設(shè)置試圖的el元素, 也會(huì)清除當(dāng)前元素的事件
undelegateEvents : function() {
this.$el.unbind('.delegateEvents' + this.cid);
},
// 在實(shí)例化視圖對(duì)象時(shí)設(shè)置初始配置
// 將傳遞的配置覆蓋到對(duì)象的options中
// 將配置中與viewOptions列表相同的配置復(fù)制到對(duì)象本身, 作為對(duì)象的屬性
_configure : function(options) {
// 如果對(duì)象本身設(shè)置了默認(rèn)配置, 則使用傳遞的配置進(jìn)行合并
if(this.options)
options = _.extend({}, this.options, options);
// 遍歷viewOptions列表
for(var i = 0, l = viewOptions.length; i < l; i++) {
// attr依次為viewOptions中的屬性名
var attr = viewOptions[i];
// 將options配置中與viewOptions相同的配置復(fù)制到對(duì)象本身, 作為對(duì)象的屬性
if(options[attr])
this[attr] = options[attr];
}
// 設(shè)置對(duì)象的options配置
this.options = options;
},
// 每一個(gè)視圖對(duì)象都應(yīng)該有一個(gè)el元素, 作為渲染的元素
// 在構(gòu)造視圖時(shí), 可以設(shè)置對(duì)象的el屬性來(lái)指定一個(gè)元素
// 如果設(shè)置的el是一個(gè)字符串或DOM對(duì)象, 則通過(guò)$方法將其創(chuàng)建為一個(gè)jQuery或Zepto對(duì)象
// 如果沒(méi)有設(shè)置el屬性, 則根據(jù)傳遞的tagName, id和className, 調(diào)用mak方法創(chuàng)建一個(gè)元素
// (新創(chuàng)建的元素不會(huì)被添加到文檔樹(shù)中, 而始終存儲(chǔ)在內(nèi)存, 當(dāng)處理完畢需要渲染到頁(yè)面時(shí), 一般會(huì)在重寫(xiě)的render方法, 或自定義方法中, 訪問(wèn)this.el將其追加到文檔)
// (如果我們需要向頁(yè)面添加一個(gè)目前還沒(méi)有的元素, 并且需要為其添加一些子元素, 屬性, 樣式或事件時(shí), 可以通過(guò)該方式先將元素創(chuàng)建到內(nèi)存, 在完成所有操作之后再手動(dòng)渲染到文檔, 可以提高渲染效率)
_ensureElement : function() {
// 如果沒(méi)有設(shè)置el屬性, 則創(chuàng)建默認(rèn)元素
if(!this.el) {
// 從對(duì)象獲取attributes屬性, 作為新創(chuàng)建元素的默認(rèn)屬性列表
var attrs = getValue(this, 'attributes') || {};
// 設(shè)置新元素的id
if(this.id)
attrs.id = this.id;
// 設(shè)置新元素的class
if(this.className)
attrs['class'] = this.className;
// 通過(guò)make方法創(chuàng)建元素, 并調(diào)用setElement方法將元素設(shè)置為視圖所使用的標(biāo)準(zhǔn)元素
this.setElement(this.make(this.tagName, attrs), false);
} else {
// 如果設(shè)置了el屬性, 則直接調(diào)用setElement方法將el元素設(shè)置為視圖的標(biāo)準(zhǔn)元素
this.setElement(this.el, false);
}
}
});
// 實(shí)現(xiàn)對(duì)象繼承的函數(shù), 該函數(shù)內(nèi)部使用inherits實(shí)現(xiàn)繼承, 請(qǐng)參考inherits函數(shù)
var extend = function(protoProps, classProps) {
// child存儲(chǔ)已經(jīng)實(shí)現(xiàn)繼承自當(dāng)前類的子類(Function)
// protoProps設(shè)置子類原型鏈中的屬性
// classProps設(shè)置子類的靜態(tài)屬性
var child = inherits(this, protoProps, classProps);
// 將extend函數(shù)添加到子類, 因此調(diào)用子類的extend方法便可實(shí)現(xiàn)對(duì)子類的繼承
child.extend = this.extend;
// 返回實(shí)現(xiàn)繼承的子類
return child;
};
// 為Model, Collection, Router和View類實(shí)現(xiàn)繼承機(jī)制
Model.extend = Collection.extend = Router.extend = View.extend = extend;
// Backbone.sync 與服務(wù)器異步交互相關(guān)
// -------------
// 定義Backbone中與服務(wù)器交互方法和請(qǐng)求type的對(duì)應(yīng)關(guān)系
var methodMap = {
'create' : 'POST',
'update' : 'PUT',
'delete' : 'DELETE',
'read' : 'GET'
};
// sync用于在Backbone中操作數(shù)據(jù)時(shí), 向服務(wù)器發(fā)送請(qǐng)求同步數(shù)據(jù)狀態(tài), 以建立與服務(wù)器之間的無(wú)縫連接
// sync發(fā)送默認(rèn)通過(guò)第三方庫(kù)(jQuery, Zepto等) $.ajax方法發(fā)送請(qǐng)求, 因此如果要調(diào)用狀態(tài)同步相關(guān)的方法, 需要第三方庫(kù)支持
// Backbone默認(rèn)定義了一套與服務(wù)器交互的數(shù)據(jù)格式(JSON)和結(jié)構(gòu), 服務(wù)器響應(yīng)的數(shù)據(jù)應(yīng)該遵循該約定
// 如果數(shù)據(jù)不需要保存在服務(wù)器, 或與服務(wù)器交互方法, 數(shù)據(jù)格式結(jié)構(gòu)與約定不一致, 可以通過(guò)重載sync方法實(shí)現(xiàn)
// @param {String} method 在Backbone中執(zhí)行的CRUD操作名稱
// @param {Model Obejct} model 需要與服務(wù)器同步狀態(tài)的模型對(duì)象
// @param {Object} options
Backbone.sync = function(method, model, options) {
// 根據(jù)CRUD方法名定義與服務(wù)器交互的方法(POST, GET, PUT, DELETE)
var type = methodMap[method];
// options默認(rèn)為一個(gè)空對(duì)象
options || ( options = {});
// params將作為請(qǐng)求參數(shù)對(duì)象傳遞給第三方庫(kù)的$.ajax方法
var params = {
// 請(qǐng)求類型
type : type,
// 數(shù)據(jù)格式默認(rèn)為json
dataType : 'json'
};
// 如果在發(fā)送請(qǐng)求時(shí)沒(méi)有在options中設(shè)置url地址, 將會(huì)通過(guò)模型對(duì)象的url屬性或方法來(lái)獲取url
// 模型所獲取url的方式可參考模型的url方法
if(!options.url) {
// 獲取請(qǐng)求地址失敗時(shí)會(huì)調(diào)用urlError方法拋出一個(gè)錯(cuò)誤
params.url = getValue(model, 'url') || urlError();
}
// 如果調(diào)用create和update方法, 且沒(méi)有在options中定義請(qǐng)求數(shù)據(jù), 將序列化模型中的數(shù)據(jù)對(duì)象傳遞給服務(wù)器
if(!options.data && model && (method == 'create' || method == 'update')) {
// 定義請(qǐng)求的Content-Type頭, 默認(rèn)為application/json
params.contentType = 'application/json';
// 序列化模型中的數(shù)據(jù), 并作為請(qǐng)求數(shù)據(jù)傳遞給服務(wù)器
params.data = JSON.stringify(model.toJSON());
}
// 對(duì)于不支持application/json編碼的瀏覽器, 可以通過(guò)設(shè)置Backbone.emulateJSON參數(shù)為true實(shí)現(xiàn)兼容
if(Backbone.emulateJSON) {
// 不支持Backbone.emulateJSON編碼的瀏覽器, 將類型設(shè)置為application/x-www-form-urlencoded
params.contentType = 'application/x-www-form-urlencoded';
// 將需要同步的數(shù)據(jù)存放在key為"model"參數(shù)中發(fā)送到服務(wù)器
params.data = params.data ? {
model : params.data
} : {};
}
// 對(duì)于不支持REST方式的瀏覽器, 可以設(shè)置Backbone.emulateHTTP參數(shù)為true, 以POST方式發(fā)送數(shù)據(jù), 并在數(shù)據(jù)中加入_method參數(shù)標(biāo)識(shí)操作名稱
// 同時(shí)也將發(fā)送X-HTTP-Method-Override頭信息
if(Backbone.emulateHTTP) {
// 如果操作類型為PUT或DELETE
if(type === 'PUT' || type === 'DELETE') {
// 將操作名稱存放到_method參數(shù)發(fā)送到服務(wù)器
if(Backbone.emulateJSON)
params.data._method = type;
// 實(shí)際以POST方式進(jìn)行提交, 并發(fā)送X-HTTP-Method-Override頭信息
params.type = 'POST';
params.beforeSend = function(xhr) {
xhr.setRequestHeader('X-HTTP-Method-Override', type);
};
}
}
// 對(duì)非GET方式的請(qǐng)求, 將不對(duì)數(shù)據(jù)進(jìn)行轉(zhuǎn)換, 因?yàn)閭鬟f的數(shù)據(jù)可能是一個(gè)JSON映射
if(params.type !== 'GET' && !Backbone.emulateJSON) {
// 通過(guò)設(shè)置processData為false來(lái)關(guān)閉數(shù)據(jù)轉(zhuǎn)換
// processData參數(shù)是$.ajax方法中的配置參數(shù), 詳細(xì)信息可參考jQuery或Zepto相關(guān)文檔
params.processData = false;
}
// 通過(guò)第三方庫(kù)的$.ajax方法向服務(wù)器發(fā)送請(qǐng)求同步數(shù)據(jù)狀態(tài)
// 傳遞給$.ajax方法的參數(shù)使用extend方法將options對(duì)象中的參數(shù)覆蓋到了params對(duì)象, 因此在調(diào)用sync方法時(shí)設(shè)置了與params同名的options參數(shù), 將以options為準(zhǔn)
return $.ajax(_.extend(params, options));
};
// 包裝一個(gè)統(tǒng)一的模型錯(cuò)誤處理方法, 會(huì)在模型與服務(wù)器交互發(fā)生錯(cuò)誤時(shí)被調(diào)用
// onError是在調(diào)用與服務(wù)器的交互方法時(shí)(如fetch, destory等), options中指定的自定義錯(cuò)誤處理函數(shù)
// originalModel是發(fā)生錯(cuò)誤的模型或集合對(duì)象
Backbone.wrapError = function(onError, originalModel, options) {
return function(model, resp) {
resp = model === originalModel ? resp : model;
if(onError) {
// 如果設(shè)置了自定義錯(cuò)誤處理方法, 則調(diào)用自定義方法
onError(originalModel, resp, options);
} else {
// 默認(rèn)將觸發(fā)發(fā)生錯(cuò)誤的模型或集合的error事件
originalModel.trigger('error', originalModel, resp, options);
}
};
};
// Helpers 定義一些供Backbone內(nèi)部使用的幫助函數(shù)
// -------
// ctor是一個(gè)共享的空函數(shù), 用于在調(diào)用inherits方法實(shí)現(xiàn)繼承時(shí), 承載父類的原型鏈以便設(shè)置到子類原型中
var ctor = function() {
};
// 實(shí)現(xiàn)OOP繼承特性
// @param {Function} parent 被繼承的父類Function
// @param {Object} protoProps 擴(kuò)展子類原型中的屬性(或方法)對(duì)象
// @param {Object} staticProps 擴(kuò)展子類的靜態(tài)屬性(或方法)對(duì)象
var inherits = function(parent, protoProps, staticProps) {
var child;
// 如果在protoProps中指定了"constructor"屬性, 則"constructor"屬性被作為子類的構(gòu)造函數(shù)
// 如果沒(méi)有指定構(gòu)造子類構(gòu)造函數(shù), 則默認(rèn)調(diào)用父類的構(gòu)造函數(shù)
if(protoProps && protoProps.hasOwnProperty('constructor')) {
// 使用"constructor"屬性指定的子類構(gòu)造函數(shù)
child = protoProps.constructor;
} else {
// 使用父類的構(gòu)造函數(shù)
child = function() {
parent.apply(this, arguments);
};
}
// 將父類中的靜態(tài)屬性復(fù)制為子類靜態(tài)屬性
_.extend(child, parent);
// 將父類原型鏈設(shè)置到子類的原型對(duì)象中, 子類以此繼承父類原型鏈中的所有屬性
ctor.prototype = parent.prototype;
child.prototype = new ctor();
// 將protoProps對(duì)象中的屬性復(fù)制到子類的原型對(duì)象, 子類以此擁有protoProps中的屬性
if(protoProps)
_.extend(child.prototype, protoProps);
// 將staticProps對(duì)象中的屬性復(fù)制到子類的構(gòu)造函數(shù)本身, 將staticProps中的屬性作為子類的靜態(tài)屬性
if(staticProps)
_.extend(child, staticProps);
// 在復(fù)制父類原型鏈到子類原型時(shí), 子類原型鏈中的構(gòu)造函數(shù)已經(jīng)被覆蓋, 因此此處重新設(shè)置子類的構(gòu)造函數(shù)
child.prototype.constructor = child;
// 如果子類設(shè)置了constructor屬性, 則子類構(gòu)造函數(shù)為constructor指定的函數(shù)
// 如果需要在子類構(gòu)造函數(shù)中調(diào)用父類構(gòu)造函數(shù), 則需要在子類構(gòu)造函數(shù)中手動(dòng)調(diào)用父類的構(gòu)造函數(shù)
// 此處將子類的__super__屬性指向父類的構(gòu)造函數(shù), 方便在子類中調(diào)用: 子類.__super__.constructor.call(this);
child.__super__ = parent.prototype;
// 返回子類
return child;
};
// 獲取對(duì)象prop屬性的值, 如果prop屬性是一個(gè)函數(shù), 則執(zhí)行并返回該函數(shù)的返回值
var getValue = function(object, prop) {
// 如果object為空或object不存在prop屬性, 則返回null
if(!(object && object[prop]))
return null;
// 返回prop屬性值, 如果prop是一個(gè)函數(shù), 則執(zhí)行并返回該函數(shù)的返回值
return _.isFunction(object[prop]) ? object[prop]() : object[prop];
};
// 拋出一個(gè)Error異常, 在Backbone內(nèi)部會(huì)頻繁執(zhí)行, 因此獨(dú)立為一個(gè)公共函數(shù)
var urlError = function() {
throw new Error('A "url" property or function must be specified');
};
}).call(this);
相關(guān)文章
輕量級(jí)javascript 框架Backbone使用指南
這篇文章主要介紹了輕量級(jí)javascript 框架Backbone使用指南的相關(guān)資料,需要的朋友可以參考下2015-07-07
JavaScript的Backbone.js框架入門(mén)學(xué)習(xí)指引
這篇文章主要介紹了JavaScript的Backbone.js框架入門(mén)學(xué)習(xí)指引, 其中特別講到了Backbone中的關(guān)鍵部分Router路由器,需要的朋友可以參考下2016-05-05
詳解Backbone.js框架中的模型Model與其集合collection
這篇文章主要介紹了Backbone.js框架中的模型Model與其集合collection,Backbone擁有與傳統(tǒng)MVC框架相類似的Model與View結(jié)構(gòu),需要的朋友可以參考下2016-05-05
Backbone.js框架中簡(jiǎn)單的View視圖編寫(xiě)學(xué)習(xí)筆記
這篇文章主要介紹了Backbone.js框架中簡(jiǎn)單的View編寫(xiě)學(xué)習(xí)筆記,Backbone是JavaScript的一款高人氣MVC框架,需要的朋友可以參考下2016-02-02
BackBone及其實(shí)例探究_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
這篇文章主要介紹了BackBone及其實(shí)例探究,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-07-07
Backbone中View之間傳值的學(xué)習(xí)心得
Backbone中的View就是用來(lái)展示由Model層傳出的數(shù)據(jù),或者在View里產(chǎn)生的一些數(shù)據(jù),本文給大家介紹Backbone中View之間傳值的解決方法,感興趣的朋友一起看下吧2016-08-08

