晚上躺在床上,继续完成对Backbone.Collection的源码解析。
首先讲讲它用来干嘛?
Backbone.Collection的实例表示一个集合,是很多model组成的,如果用model比喻成数据表中的row,那么collection就是那张数据表。
在mvc单页面程序里面,我们不可能只用一条一条的数据,我们更需要多条数据的处理,并且能够统一的管理这多条数据,无论是网络请求还是前端交互。
就好比前端有一个datagrid,很多row的数据,可以抽象成一个collection,row和collection之间就存在这联系,如果每一个row发生了变化,那么collection就
需要知道这个变化,而且根据这个变化做出相应的处理,就比如说添加,删除,更新等操作。
好了,话不多说,还是给上源码注释把,之后会给出一些demo,来具体演示一下
// Backbone.Collection
// ------------------- // If models tend to represent a single row of data, a Backbone Collection is
// more analagous to a table full of data ... or a small slice or page of that
// table, or a collection of rows that belong together for a particular reason
// -- all of the messages in this particular folder, all of the documents
// belonging to this particular author, and so on. Collections maintain
// indexes of their models, both in order, and for lookup by `id`. // Create a new **Collection**, perhaps to contain a specific type of `model`.
// If a `comparator` is specified, the Collection will maintain
// its models in sort order, as they're added and removed.
// 既然Model实例是数据库中,某一个数据表的,某一行数据,
// 那么Collection实例就可以理解为这个数据库中的,这个数据表,这张数据表每一行的数据格式都是一样的
var Collection = Backbone.Collection = function(models, options) { // 代码兼容
options || (options = {}); // 初始化Collection实例时,如果设置了options.model,用options.model代替this.model
// this.model默认值是Backbone.Model,也可以是用户继承Backbone.Collection时可以覆盖重写,
// 显然this.model是一个构造函数,是构造Collection实例中的每一个Model元素的构造函数,
// 它的用途在于如果参数models数组里面的传递的不是Model实例,而是一些属性列表(hash),那么就可以
// 将这些属性列表传入this.model构造函数来创建model实例
if (options.model) this.model = options.model; // comparator用来对对集合元素进行排序,可以使函数,也可以是某个属性名
if (options.comparator !== void 0) this.comparator = options.comparator; // 初始化对象的一些属性
this._reset(); // 调用initialize函数用于初始化,一般initialize用于用户自定义
this.initialize.apply(this, arguments); // 如果构造时,传递给Collection实例models,那么向Collection实例中添加这些models
// {silent: true} 表示不触发reset事件
// 个人觉得这里用 this.add(models, _.extend({silent: true}, options))也可以
// 可能主要是一个严谨问题,reset方法内部会对做清理工作
// 因为有可能用户会这样使用:
// Backbone.Collection.apply(CollectionObj, models, options);
// CollectionObj为已经初始化的Collection实例并且已经包含多个models
if (models) this.reset(models, _.extend({silent: true}, options));
}; // Default options for `Collection#set`.
// 这里是一些配置,为了方便使用
// 分别是set操作和add操作的默认配置
// 显然是默认情况下:
// set操作:add新model,remove旧model,merge旧model
// add操作:add新model
// 要想打破这个束缚,只要在调用set和add方法时,传递对应的options参数即可
var setOptions = {add: true, remove: true, merge: true};
var addOptions = {add: true, remove: false}; // Define the Collection's inheritable methods.
// 定义一些Collection的原型方法
_.extend(Collection.prototype, Events, { // The default model for a collection is just a **Backbone.Model**.
// This should be overridden in most cases.
// Collection实例里元素的构造函数
// 简单点说就是Collection实例的models都是由Backbone.Model构造出来的
// 显然这个值大部分情况下需要被覆盖重写,有两种方法
// 1. new Backbone.Collection(models,{model:CustomModel});
// 2. CustomCollection = Backbone.extend({model:CustomModel});
model: Model, // Initialize is an empty function by default. Override it with your own
// initialization logic.
// 初始化函数,用户根据需求自定义重写
initialize: function(){}, // The JSON representation of a Collection is an array of the
// models' attributes.
// 以数组格式鲜明的显示集合数据
toJSON: function(options) {
return this.map(function(model){ return model.toJSON(options); });
}, // Proxy `Backbone.sync` by default.
// ajax接口,用于异步获取数据,可以根据需求覆盖重写
sync: function() {
return Backbone.sync.apply(this, arguments);
}, // Add a model, or list of models to the set.
// 向集合中添加一个model或多个model(数组)
add: function(models, options) {
return this.set(models, _.extend({merge: false}, options, addOptions));
}, // Remove a model, or a list of models from the set.
// 从集合中删除一个model或多个model(数组)
// 注意这里的每一个model可以是model实例,model.id或者model.cid
// Collection的get方法会作处理
remove: function(models, options) { // 统一转换成数组处理
var singular = !_.isArray(models); models = singular ? [models] : _.clone(models);
options || (options = {}); var i, l, index, model; // 遍历删除
for (i = 0, l = models.length; i < l; i++) { // 从集合中获取指定的model
model = models[i] = this.get(models[i]); // 如果model不存在,那么continue
if (!model) continue; // 否则删除该model在集合中的相关信息
// 从集合里删除该model
delete this._byId[model.id];
delete this._byId[model.cid];
index = this.indexOf(model);
this.models.splice(index, 1);
this.length--; // 触发remove事件
if (!options.silent) { // 删除时该model的索引,通过options传递给callback
options.index = index;
model.trigger('remove', model, this, options);
} // 删除该model与该集合的联系
// 因为可能删除的这个model可能被用于其他Collection实例
this._removeReference(model);
} // 返回删除后的model(集合)
return singular ? models[0] : models;
}, // Update a collection by `set`-ing a new list of models, adding new ones,
// removing models that are no longer present, and merging models that
// already exist in the collection, as necessary. Similar to **Model#set**,
// the core operation for updating the data contained by the collection.
set: function(models, options) { // 配置选项,默认值是setOptions
options = _.defaults({}, options, setOptions); // 解析或者过滤,并返回指定格式数据
if (options.parse) models = this.parse(models, options); // 统一转换成数组处理
var singular = !_.isArray(models);
models = singular ? (models ? [models] : []) : _.clone(models); var i, l, id, model, attrs, existing, sort; // at表示model索引
// targetModel表示集合元素的类型
var at = options.at;
var targetModel = this.model; // 是否排序
// 注意:如果指定了at,那么排序就无意义
// 也可以手动通过设置options.sort来阻止排序
var sortable = this.comparator && (at == null) && options.sort !== false; // 按某个属性值排序
var sortAttr = _.isString(this.comparator) ? this.comparator : null; var toAdd = [], toRemove = [], modelMap = {};
var add = options.add, merge = options.merge, remove = options.remove; // (!sortable && add && remove) ? [] : false
// 这里的order有什么用?
// 想了半天我的理解为:
// 在不需要进行排序的的大前提下,且add和remove都为true时,
// 使用order数组有序存储修正过的models,最后使用将集合的元素清空,
// 直接添加order数组到集合中,可以保证集合中元素的顺序和添加时元素的顺序一致
// 注意:只有add和remove为true时,后面才可以先进行清空操作(this.models.length = 0;)
// 然后再在最后添加order数组,从而保证元素的个数和顺序都正确无误
var order = !sortable && add && remove ? [] : false; // Turn bare objects into model references, and prevent invalid models
// from being added.
// 遍历处理models或者属性列表(hash)
for (i = 0, l = models.length; i < l; i++) { attrs = models[i]; // 如果attrs是Model实例(这里有可能是子类实例)
// 注意这里model被负值,接下来可能作为判断条件
if (attrs instanceof Model) {
id = model = attrs; // 如果是属性列表(hash)根据idAttribute获取id
} else {
id = attrs[targetModel.prototype.idAttribute];
} // If a duplicate is found, prevent it from being added and
// optionally merge it into the existing model.
// 通过id进行查找当前集合是否存在该model
// 这里的id显然可以是model实例,也可以id字符串 // 如果该model已存在
if (existing = this.get(id)) { // 通过保存cid,为下面remove操作删除model做准备
if (remove) modelMap[existing.cid] = true; // 如果合并则...执行接下来...
// 如果不合并,那么将被忽略
if (merge) { // 获取属性列表
attrs = attrs === model ? model.attributes : attrs; // 过滤属性列表
if (options.parse) attrs = existing.parse(attrs, options); // 设置属性列表
existing.set(attrs, options); // sort是后面用来标识是否排序
// 条件判断:
// sortable表示集合设置了排序
// !sort表示还没有被设置
// existing.hasChanged(sortAttr)表示model的排序属性有变化过
if (sortable && !sort && existing.hasChanged(sortAttr)) sort = true;
} // 重新负值修改过的model
models[i] = existing; // If this is a new, valid model, push it to the `toAdd` list.
// 如果该model不存在,且add
} else if (add) { // 格式化attrs,统一转换为该符合该集合的model实例
model = models[i] = this._prepareModel(attrs, options); // model为false,表示转换失败
if (!model) continue; // 添加到toAdd列表中,等候处理
toAdd.push(model); // Listen to added models' events, and index models for lookup by
// `id` and by `cid`.
// 给集合中的每个model都添加all事件
// 这样的话只要model发生任何事件都会通知集合,进而可以进行相关操作
model.on('all', this._onModelEvent, this); // 设置索引,方便获取某个model
this._byId[model.cid] = model; // 如果服务端数据有返回唯一键
if (model.id != null) this._byId[model.id] = model;
} if (order) order.push(existing || model);
} // Remove nonexistent models if appropriate.
// 删除model
if (remove) { // 遍历集合,通过上面搜集的modelMap进行用cid映射删除对应的model
for (i = 0, l = this.length; i < l; ++i) {
if (!modelMap[(model = this.models[i]).cid]) toRemove.push(model);
}
if (toRemove.length) this.remove(toRemove, options);
} // See if sorting is needed, update `length` and splice in new models.
// 添加model
if (toAdd.length || (order && order.length)) { if (sortable) sort = true; this.length += toAdd.length; // 添加model到指定位置
if (at != null) {
for (i = 0, l = toAdd.length; i < l; i++) {
this.models.splice(at + i, 0, toAdd[i]);
} // 直接通过push添加model
} else { // 如果order存在,那么先进行清空操作,再添加整个order
if (order) this.models.length = 0; var orderedModels = order || toAdd;
for (i = 0, l = orderedModels.length; i < l; i++) {
this.models.push(orderedModels[i]);
}
}
} // Silently sort the collection if appropriate.
// 排序
if (sort) this.sort({silent: true}); // Unless silenced, it's time to fire all appropriate add/sort events.
// 触发每一个model的add事件
if (!options.silent) {
for (i = 0, l = toAdd.length; i < l; i++) {
(model = toAdd[i]).trigger('add', model, this, options);
}
// 如果排序或者order存在,那么触发sort事件
// 注意:order存在表示用户没有定义comparator等,那么就可以通过sort事件
// 对添加进来的models做个排序处理等
if (sort || (order && order.length)) this.trigger('sort', this, options);
} // Return the added (or merged) model (or models).
// 返回修改添加过的model(集合)
return singular ? models[0] : models;
}, // When you have more items than you want to add or remove individually,
// you can reset the entire set with a new list of models, without firing
// any granular `add` or `remove` events. Fires `reset` when finished.
// Useful for bulk operations and optimizations.
// 用新一批的models重置当前的集合,也就是说集合中已存在的旧models会全被删除掉
// 这样的好处在于不用一个个去删除,再去添加
reset: function(models, options) { options || (options = {}); // 删除旧models与集合的联系
for (var i = 0, l = this.models.length; i < l; i++) {
this._removeReference(this.models[i]);
} // 将旧models通过options对象传递给reset事件触发的callback
options.previousModels = this.models; // 清空操作
this._reset(); // 调用add方法,添加新一批models
models = this.add(models, _.extend({silent: true}, options)); // 触发集合的reset事件
if (!options.silent) this.trigger('reset', this, options); return models;
}, // Add a model to the end of the collection.
// 添加model到集合的尾部(利用at参数)
// 当然这里的model也可以是一个数组(添加后保持原有的顺序)
push: function(model, options) {
return this.add(model, _.extend({at: this.length}, options));
}, // Remove a model from the end of the collection.
// 删除集合最后一个model
pop: function(options) {
var model = this.at(this.length - 1);
this.remove(model, options);
return model;
}, // Add a model to the beginning of the collection.
// 添加model到集合的头部(利用at参数)
// 当然这里的model也可以是一个数组(添加后保持原有的顺序)
unshift: function(model, options) {
return this.add(model, _.extend({at: 0}, options));
}, // Remove a model from the beginning of the collection.
// 删除集合第一个model
shift: function(options) {
var model = this.at(0);
this.remove(model, options);
return model;
}, // Slice out a sub-array of models from the collection.
// 因为this.models是一个数组,可以才用slice方法进行截取
slice: function() {
return slice.apply(this.models, arguments);
}, // Get a model from the set by id.
// 获取集合中的model
// 参数obj可以是model实例,cid,id
get: function(obj) {
if (obj == null) return void 0;
return this._byId[obj.id] || this._byId[obj.cid] || this._byId[obj];
}, // Get the model at the given index.
// 返回集合中指定索引的model
at: function(index) {
return this.models[index];
}, // Return models with matching attributes. Useful for simple cases of
// `filter`.
// 返回匹配attrs属性列表的model(集合)
// first表示只返回匹配结果的第一个
where: function(attrs, first) {
if (_.isEmpty(attrs)) return first ? void 0 : [];
return this[first ? 'find' : 'filter'](function(model) {
for (var key in attrs) {
if (attrs[key] !== model.get(key)) return false;
}
return true;
});
}, // Return the first model with matching attributes. Useful for simple cases
// of `find`.
// 返回第一个匹配attrs属性列表的结果
findWhere: function(attrs) {
return this.where(attrs, true);
}, // Force the collection to re-sort itself. You don't need to call this under
// normal circumstances, as the set will maintain sort order as each item
// is added.
// 给集合中的models排序
sort: function(options) { // 根据comparator排序
if (!this.comparator) throw new Error('Cannot sort a set without a comparator');
options || (options = {}); // Run sort based on type of `comparator`.
// 如果comparator是字符串(属性名),或者参数为1的函数,用underscore的sortBy函数排序
if (_.isString(this.comparator) || this.comparator.length === 1) {
this.models = this.sortBy(this.comparator, this); // 负责数组自带的sort函数排序
} else {
this.models.sort(_.bind(this.comparator, this));
} // 触发sort事件
if (!options.silent) this.trigger('sort', this, options);
return this;
}, // Pluck an attribute from each model in the collection.
// 返回由所有models的某个属性组成的数组
pluck: function(attr) {
return _.invoke(this.models, 'get', attr);
}, // Fetch the default set of models for this collection, resetting the
// collection when they arrive. If `reset: true` is passed, the response
// data will be passed through the `reset` method instead of `set`.
// 从服务器端获取数据填充集合
fetch: function(options) { options = options ? _.clone(options) : {}; // 服务器端过来的数据需要parse一下,传递正确格式的数据(models)
if (options.parse === void 0) options.parse = true; var success = options.success;
var collection = this; // 成功回调
options.success = function(resp) { // 服务器端获取过来的数据,集合采取reset还是set方式处理数据?
var method = options.reset ? 'reset' : 'set';
collection[method](resp, options); // 用户自定义回调
if (success) success(collection, resp, options); // 触发sync事件
collection.trigger('sync', collection, resp, options);
}; // 失败回调的包装
wrapError(this, options); // 开始读取数据
return this.sync('read', this, options);
}, // Create a new instance of a model in this collection. Add the model to the
// collection immediately, unless `wait: true` is passed, in which case we
// wait for the server to agree.
// 创建model实例,然后添加到集合中,最后存储到服务器端
create: function(model, options) { options = options ? _.clone(options) : {}; // 转换为model实例
if (!(model = this._prepareModel(model, options))) return false; // wait参数,表示是否先在前端先展示
if (!options.wait) this.add(model, options); var collection = this;
var success = options.success; // 成功回调
options.success = function(model, resp, options) {
if (options.wait) collection.add(model, options);
if (success) success(model, resp, options);
}; // 保存到服务端
model.save(null, options);
return model;
}, // **parse** converts a response into a list of models to be added to the
// collection. The default implementation is just to pass it through.
// 过滤解析当前models或是从服务端返回的数据
parse: function(resp, options) {
return resp;
}, // Create a new collection with an identical list of models as this one.
// 克隆当前集合
clone: function() {
return new this.constructor(this.models);
}, // Private method to reset all internal state. Called when the collection
// is first initialized or reset.
// 重置Collection实例的一些内部变量(状态)
_reset: function() {
this.length = 0;
this.models = [];
this._byId = {};
}, // Prepare a hash of attributes (or other model) to be added to this
// collection.
// 前面说过,初始化时的models,也就是这里的attrs可以时属性列表(hash)
// 这里还可以是其他model,那么需要统一处理成该集合应该包含的model实例
_prepareModel: function(attrs, options) { // 如果是model实例
if (attrs instanceof Model) { // 设置collection,与集合建立联系
if (!attrs.collection) attrs.collection = this;
return attrs;
} // 如果是属性列表
options = options ? _.clone(options) : {}; // 设置与集合的联系
options.collection = this; // 创建model实例
var model = new this.model(attrs, options); if (!model.validationError) return model; // 创建失败,触发集合的invalida事件,并返回false
this.trigger('invalid', this, model.validationError, options);
return false;
}, // Internal method to sever a model's ties to a collection.
// 删除某个model与集合的联系
_removeReference: function(model) {
if (this === model.collection) delete model.collection;
model.off('all', this._onModelEvent, this);
}, // Internal method called every time a model in the set fires an event.
// Sets need to update their indexes when models change ids. All other
// events simply proxy through. "add" and "remove" events that originate
// in other collections are ignored.
// 因为在向集合中添加model实例时,会给每一个model实例绑定一个all事件,
// 对应的回调函数就是_onModelEvent,也就是说,每个model的(属性)变化都会
// 触发该函数,该函数只要根据event是什么,做出相应的处理就可以了
_onModelEvent: function(event, model, collection, options) { // 如果collection不是集合本身,过滤掉
if ((event === 'add' || event === 'remove') && collection !== this) return; // model被destory后,从集合中remove掉即可
if (event === 'destroy') this.remove(model, options); // 如果model的唯一键(服务器端)发生变化,需要修改this._byId的映射
if (model && event === 'change:' + model.idAttribute) {
delete this._byId[model.previous(model.idAttribute)];
if (model.id != null) this._byId[model.id] = model;
} // 触发集合对应的事件
this.trigger.apply(this, arguments);
} }); // Underscore methods that we want to implement on the Collection.
// 90% of the core usefulness of Backbone Collections is actually implemented
// right here:
var methods = ['forEach', 'each', 'map', 'collect', 'reduce', 'foldl',
'inject', 'reduceRight', 'foldr', 'find', 'detect', 'filter', 'select',
'reject', 'every', 'all', 'some', 'any', 'include', 'contains', 'invoke',
'max', 'min', 'toArray', 'size', 'first', 'head', 'take', 'initial', 'rest',
'tail', 'drop', 'last', 'without', 'difference', 'indexOf', 'shuffle',
'lastIndexOf', 'isEmpty', 'chain']; // Mix in each Underscore method as a proxy to `Collection#models`.
// 因为集合的models是一个数组,那么underscore的一系列方法都可以附加到Collection的原型上,
// 可以方便处理集合
_.each(methods, function(method) {
Collection.prototype[method] = function() {
var args = slice.call(arguments);
args.unshift(this.models);
return _[method].apply(_, args);
};
}); // Underscore methods that take a property name as an argument.
var attributeMethods = ['groupBy', 'countBy', 'sortBy']; // Use attributes instead of properties.
// 同样是间接利用underscore的方法,只是做了一些简单处理
_.each(attributeMethods, function(method) {
Collection.prototype[method] = function(value, context) {
var iterator = _.isFunction(value) ? value : function(model) {
return model.get(value);
};
return _[method](this.models, iterator, context);
};
});
好了,各位晚安,明天又是周五了,开心~~