Collection模块简介
Collection模块是对Model模块的创建、存储与删除等操作的一个集合。其中提供了对Model进行方便管理的各种方法(包括从Underscore.js扩展来的各种操作方法),实现了对一个应用中有多个数据模型的情形下的便利操作。
Collection模块源码注释解析
// Backbone.Collection
// -------------------
// If models tend to represent a single row of data, a Backbone Collection is
// more analogous 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.
// 如果模型代表着一个单一的数据行,那么Backbone的Collection就是一个由行数据组成的表格,或者说是这个表格的一部分或一页,或者是具有一个特定特性的行数据的集合。
// 集合通过"id"来查找model,并对model进行有序的维护。可以通过包含一些特定类型的model来创建一个新的集合。
// 如果指定了`comparator`,那么集合里的model就是有序的,即使某个model被添加和被删除,整个集合的model也会保持有序性。
var Collection = Backbone.Collection = function(models, options) {// 开始创建集合类
options || (options = {});// 保证该上下文环境中的options是一个可用的对象
// 根url,通过该url并将model的id附加在链接末尾来跟服务器同步数据(可见Model的url方法)
if (options.url) this.url = options.url;
// 在配置参数中设置集合的模型类,默认是Backbone.Model
if (options.model) this.model = options.model;
// comparator作为排序的准则,如果在选项参数中指定,则将其赋给Collection的实例
if (options.comparator !== void 0) this.comparator = options.comparator;
// 初始化集合,指定几个内部私有工具属性
this._reset();
// 执行初始化方法
this.initialize.apply(this, arguments);
// 若指定了初始化时要添加的models,则添加进去
if (models) this.reset(models, _.extend({silent: true}, options));
};
// Default options for `Collection#set`.
// 定义Collection的默认操作,方便后面扩展
var setOptions = {add: true, remove: true, merge: true};
var addOptions = {add: true, merge: false, remove: false};
// 定义Collection可继承的方法
_.extend(Collection.prototype, Events, {
// 对于Collection来说,其默认的model实现类是Backbone.Model。大多数情况下该属性都应该被重写
model: Model,
// 在new一个Collection实例的时候都会自动执行该方法。它默认是一个空的方法,你可以用自己的初始化逻辑来覆盖它。
initialize: function(){},
// 返回一个数组, 包含了集合中每个模型的数据对象
toJSON: function(options) {
// 通过Undersocre的map方法将集合中每一个模型的toJSON结果组成一个数组, 并返回
return this.map(function(model){
// 依次调用每个模型对象的toJSON方法, 该方法默认将返回模型的数据对象(复制的副本)
// 如果需要返回字符串等其它形式, 可以重载toJSON方法
return model.toJSON(options);
});
},
// 默认使用Backbone.sync来实现集合的sync
sync: function() {
return Backbone.sync.apply(this, arguments);
},
// 往集合中添加一个或者几个model实例,调用set方法来实现
add: function(models, options) {
// _.defaults:将第一个参数对象中属性值为undefined的属性用第二个参数对象的属性填充起来
return this.set(models, _.defaults(options || {}, addOptions));
},
// Remove a model, or a list of models from the set.
// 从Collection集合中移除一个或一系列的model实例
remove: function(models, options) {
// 将models转化为数组,如果models已经是数组则返回它的副本,以防破坏原来的数组
models = _.isArray(models) ? models.slice() : [models];
// 处理参数常见做法,给options默认值
options || (options = {});
var i, l, index, model;
// 遍历每一个model实例(据说从后往前遍历一个数组速度会更快)
for (i = 0, l = models.length; i < l; i++) {
// 以model的id获取到改model实例
model = this.get(models[i]);
// 如果没有找到该model则跳出本次循环
if (!model) continue;
// 找到了就将对应该id/cid的model从集合中删除
delete this._byId[model.id];
delete this._byId[model.cid];
// 获取到改model在集合中的位置
index = this.indexOf(model);
// 将其从models数组中删除
this.models.splice(index, 1);
// 实时修改长度记录值
this.length--;
// 如果没有设置silent参数则触发"remove"事件
if (!options.silent) {
options.index = index;
// 触发"remove"事件
model.trigger('remove', model, this, options);
}
// 内部方法,用于将model实例对集合的引用删除掉,并去除掉有关集合的监听事件
this._removeReference(model);
}
// 返回this用于链式调用
return this;
},
// 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.
// 更新集合的方法。可以增添一个模型列表,一个模型,删除不再使用的模型,合并已经存在于集合中的模型。
// 默认会触发"all"事件, 如果在options中设置了silent属性, 可以关闭此次事件触发
// 传入的models可以是一个或一系列的模型对象(Model类的实例), 如果在集合中设置了model属性, 则允许直接传入数据对象(如 {name: 'test'}), 将自动将数据对象实例化为model指向的模型对象
set: function(models, options) {
// 使用setOptions来填充options中不存在的值
options = _.defaults(options || {}, setOptions);
// 如果options指定了parse方法(该方法一般自己扩展),则调用集合的parse方法
if (options.parse) models = this.parse(models, options);
// 将models设置为一个数组
if (!_.isArray(models)) models = models ? [models] : [];
var i, l, model, attrs, existing, sort;
// 设置新模型列表插入到集合中的位置, 如果在options中设置了at参数, 则在集合的at位置插入
// 默认将插入到集合的末尾
var at = options.at;
// 如果设置了comparator自定义排序方法, 则设置at后还将按照comparator中的方法进行排序, 因此最终的顺序可能并非在at指定的位置
// 指定是否要排序
var sortable = this.comparator && (at == null) && options.sort !== false;
// sortAttr设置为排序指标,指定其必须为字符串
var sortAttr = _.isString(this.comparator) ? this.comparator : null;
var toAdd = [], toRemove = [], modelMap = {};
// Turn bare objects into model references, and prevent invalid models from being added.
// 将空对象转换为模型引用,阻止非法模型对象被添加到集合中
for (i = 0, l = models.length; i < l; i++) {
// 将数据对象转化为模型,如果数据不合法将直接跳过此模型不做设置
if (!(model = this._prepareModel(models[i], options))) continue;
// If a duplicate is found, prevent it from being added and
// optionally merge it into the existing model.
// 如果模型的副本已经存在,将不再对模型对象做单独添加,而是将该模型与已存在的模型做合并
if (existing = this.get(model)) {
// 如果参数中设置了remove参数,则将已经存在的model的id记录在modelMap中,后续做删除时不会将此模型删除
if (options.remove) modelMap[existing.cid] = true;
if (options.merge) {
// 做merge操作
existing.set(model.attributes, options);
// 重新做排序的条件,如果原有model没有做任何改变也不做重新排序,减少不必要排序来提升性能
if (sortable && !sort && existing.hasChanged(sortAttr)) sort = true;
}
// This is a new model, push it to the `toAdd` list.
// 全新的model,将它放入toAdd列表中,后面做统一添加
} else if (options.add) {
toAdd.push(model);
// Listen to added models' events, and index models for lookup by
// `id` and by `cid`.
// 添加新的model会触发all事件
model.on('all', this._onModelEvent, this);
// 可以按model的cid找到该模型
this._byId[model.cid] = model;
// 如果自己指定了模型id则使用自己指定的id值做索引
if (model.id != null) this._byId[model.id] = model;
}
}
// Remove nonexistent models if appropriate.
// 模型删除操作
if (options.remove) {
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.
// 模型添加操作
if (toAdd.length) {
// 如果指定了排序,则可以既进行排序(对已经存在的模型进行了更新也会进行排序)
if (sortable) sort = true;
// 记录集合中的模型数量
this.length += toAdd.length;
// 指定了模型存放的位置,则将所有的模型从指定位置开始添加
if (at != null) {
splice.apply(this.models, [at, 0].concat(toAdd));
} else {
// 没有制定添加的位置则添加在列表末尾
push.apply(this.models, toAdd);
}
}
// Silently sort the collection if appropriate.
// 静态的进行排序
if (sort) this.sort({silent: true});
// 如果设置了不触发监听事件,则返回,否则触发add和sort事件
if (options.silent) return this;
// Trigger `add` events.
// 触发所有的add事件
for (i = 0, l = toAdd.length; i < l; i++) {
(model = toAdd[i]).trigger('add', model, this, options);
}
// Trigger `sort` if the collection was sorted.
if (sort) this.trigger('sort', this, options);
// 链式调用
return this;
},
// 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
// models应该是一个数组, 可以包含一系列Model模型对象, 或原始对象(将在add方法中自动创建为模型对象)
reset: function(models, options) {
// models是进行替换的模型(或数据)数组
options || (options = {});
// 遍历当前集合中的模型, 依次删除并解除它们与集合的引用关系
for (var i = 0, l = this.models.length; i < l; i++) {
this._removeReference(this.models[i]);
}
// 将以前的models做缓存
options.previousModels = this.models;
// 删除集合数据并重置状态
this._reset();
// 通过add方法将新的模型数据添加到集合
// 这里通过exnted方法将配置项覆盖到一个新的对象, 该对象默认silent为true, 因此不会触发"add"事件
// 如果在调用reset方法时没有设置silent属性则会触发reset事件, 如果设置为true则不会触发任何事件, 如果设置为false, 将依次触发"add"和"reset"事件
this.add(models, _.extend({silent: true}, options));
// 如果在调用reset方法时没有设置silent属性, 则触发reset事件
if (!options.silent) this.trigger('reset', this, options);
return this;
},
// 模仿数组方法,讲一个model实例存放于集合的尾处
push: function(model, options) {
// 准备model实例(model可以是Model的实例,也可以是要set给model的数据)
model = this._prepareModel(model, options);
// 将model实例添加到集合
this.add(model, _.extend({at: this.length}, options));
return model;
},
// 从集合的尾部删除一个model实例
pop: function(options) {
// 通过索引获取到对应的model
var model = this.at(this.length - 1);
// 将model从集合中移除
this.remove(model, options);
return model;
},
// 将model从集合的头部加入
unshift: function(model, options) {
model = this._prepareModel(model, options);
// 调用add方法将模型插入到集合的第一个位置(设置at为0)
// 如果定义了comparator排序方法, 集合的顺序将被重排
this.add(model, _.extend({at: 0}, options));
// 返回模型对象
return model;
},
// 移除并返回集合中的第一个模型对象
shift: function(options) {
// 获得集合中的第一个模型
var model = this.at(0);
// 从集合中删除该模型
this.remove(model, options);
// 返回模型对象
return model;
},
// 从集合的models中取出一个子数组
slice: function(begin, end) {
return this.models.slice(begin, end);
},
// 从集合中通过id来获取model
get: function(obj) {
// 如果没有传入obj则返回undefined
if (obj == null) return void 0;
// this._byId为集合中以id为属性存model的对象集合。obj参数可以是带有id的对象也可以直接是id
return this._byId[obj.id != null ? obj.id : obj.cid || obj];
},
// 通过索引返回对应的model实例
at: function(index) {
return this.models[index];
},
// Return models with matching attributes. Useful for simple cases of `filter`.
// 返回匹配attrs中属性的model集合,对于过滤model数据很有用
where: function(attrs, first) {
// 如果attrs是一个空对象,如果传入了first参数则返回undefined,否则返回空数组
if (_.isEmpty(attrs)) return first ? void 0 : [];
// 通过集合实例的find(如果要找出第一个符合条件的)或filter(找到所有符合条件的)来找到最终的结果
return this[first ? 'find' : 'filter'](function(model) {
for (var key in attrs) {
// 将attrs中的验证规则与集合中的模型进行匹配
if (attrs[key] !== model.get(key)) return false;
}
return true;
});
},
// 找到第一个匹配attrs条件的model。
findWhere: function(attrs) {
return this.where(attrs, true);
},
// 对集合中的模型按照comparator属性指定的方法进行排序
// 如果没有在options中设置silent参数, 则排序后将触发reset事件
sort: function(options) {
// 排序的指标必须已经存在
if (!this.comparator) throw new Error('Cannot sort a set without a comparator');
// options默认是一个对象
options || (options = {});
// comparator属性是一个字符串或者拥有值为1的length属性
if (_.isString(this.comparator) || this.comparator.length === 1) {
// 默认使用underscore的sortBy方法来进行排序
this.models = this.sortBy(this.comparator, this);
} else {
// comparator可能是一个函数的情形,调用原生的sort方法
this.models.sort(_.bind(this.comparator, this));
}
// 如果没有制定silent属性,则触发sort事件
if (!options.silent) this.trigger('sort', this, options);
// 返回this以实现链式调用
return this;
},
// Figure out the smallest index at which a model should be inserted so as
// to maintain order.
// 获取一个model应该再集合中插入的位置,以保持顺序性
sortedIndex: function(model, value, context) {
// 没传入value则将value默认为集合的comprater值
value || (value = this.comparator);
// 排序的迭代器
var iterator = _.isFunction(value) ? value : function(model) {
return model.get(value);
};
// 调用underscore的排序方法来进行排序
return _.sortedIndex(this.models, model, iterator, context);
},
// 将集合中所有模型的attr属性值存放到一个数组并返回
pluck: function(attr) {
// underscore中的invoke方法,对this.models中的每一个子项都执行get方法,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`.
// 从服务器获取集合的初始化数据
// 如果在options中设置参数reset: true, 则获取到的数据会替换集合中的当前数据, 否则将以服务器返回的数据被追加到集合中
fetch: function(options) {
// 复制options对象, 因为options对象在后面会被修改用于临时存储数据
options = options ? _.clone(options) : {};
if (options.parse === void 0) options.parse = true;
// 自定义回调函数, 数据请求成功后并添加完成后, 会调用自定义success函数
var success = options.success;
// collection记录当前集合对象, 用于在success回调函数中使用
var collection = this;
// 当从服务器请求数据成功时执行options.success, 该函数中将解析并添加数据
options.success = function(resp) {
// 通过parse方法对服务器返回的数据进行解析, 如果需要自定义数据结构, 可以重载parse方法
// 如果在options中设置reset=true, 则调用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方法处理错误事件
wrapError(this, options);
// 调用集合的sync方法(其实是Backbone的)发送请求从服务器获取数据
// 如果需要的数据并不是从服务器获取, 或获取方式不使用AJAX, 可以重载Backbone.sync方法
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.
// 在集合中创建一个模型的新的实例
// 如果在options中声明了wait属性, 则会在服务器创建成功后再将模型添加到集合, 否则先将模型添加到集合, 再保存到服务器(无论保存是否成功)
create: function(model, options) {
// 同样对options实现一份拷贝,以免修改原来对象
options = options ? _.clone(options) : {};
// 通过_prepareModel获取模型类的实例,创建模型失败则直接返回
if (!(model = this._prepareModel(model, options))) return false;
// 如果没有声明wait属性, 则通过add方法将模型添加到集合中
if (!options.wait) this.add(model, options);
var collection = this;
// success存储保存到服务器成功之后的自定义回调函数(通过options.success声明)
var success = options.success;
options.success = function(resp) {
// 如果声明了wait属性, 则在只有在服务器保存成功后才会将模型添加到集合中
if (options.wait) collection.add(model, options);
// 如果声明了自定义成功回调, 则执行自定义函数
if (success) success(model, resp, options);
};
// 调用模型的save方法, 将模型数据保存到服务器
model.save(null, options);
// 将创建的model返回
return model;
},
// 数据解析方法, 用于将服务器数据解析为模型和集合可用的结构化数据
// 默认将返回resp本身, 这需要与服务器定义Backbone支持的数据格式, 如果需要自定义数据格式, 可以重载parse方法
parse: function(resp, options) {
return resp;
},
// 返回集合类的一个实例
clone: function() {
return new this.constructor(this.models);
},
// 内部方法来重置内部所有的状态。当集合首次被初始化或者被重置时会执行
_reset: function() {
this.length = 0;
this.models = [];
this._byId = {};
},
// Prepare a hash of attributes (or other model) to be added to this collection.
// 将模型添加到集合中之前的一些准备工作
// 包括将数据实例化为一个模型对象, 和将集合引用到模型的collection属性
_prepareModel: function(attrs, options) {
// 判断如果attrs是Model的实例
if (attrs instanceof Model) {
// 为该model实例添加对当前collection实例的引用
if (!attrs.collection) attrs.collection = this;
return attrs;
}
// 为options设置默认值
options || (options = {});
// 为options添加对当前集合实例的饮用
options.collection = this;
// 创建model实例
var model = new this.model(attrs, options);
// 添加model数据值检测,如果检测没有通过则触发invalidate事件,并返回false
if (!model._validate(attrs, options)) {
this.trigger('invalid', this, attrs, options);
return false;
}
return model;
},
// 将model中用于对Collection的引用删除掉,并去除所有有关集合的事件
_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.
// 内部方法,用于在每次往集合中set模型的时候触发事件统一管理。
// 用于监听集合中模型的事件, 当模型在触发事件(add, remove, destroy, change事件)时集合进行相关处理
_onModelEvent: function(event, model, collection, options) {
// 其他集合的add和remove事件被忽略
if ((event === 'add' || event === 'remove') && collection !== this) return;
// 模型触发销毁事件时, 从集合中移除
if (event === 'destroy') this.remove(model, options);
// 当模型的id被修改时, 集合修改_byId中存储对模型的引用, 保持与模型id的同步, 便于使用get()方法获取模型对象
if (model && event === 'change:' + model.idAttribute) {
// 获取模型在改变之前的id, 并根据此id从集合的_byId列表中移除
delete this._byId[model.previous(model.idAttribute)];
// 以模型新的id作为key, 在_byId列表中存放对模型的引用
if (model.id != null) this._byId[model.id] = model;
}
// 在集合中触发模型对应的事件, 无论模型触发任何事件, 集合都会触发对应的事件
// (例如当模型被添加到集合中时, 会触发模型的"add"事件, 同时也会在此方法中触发集合的"add"事件)
// 这对于监听并处理集合中模型状态的变化非常有效
// 在监听的集合事件中, 触发对应事件的模型会被作为参数传递给集合的监听函数
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:
// 定义Underscore中的集合操作的相关方法
// 将Underscore中一系列集合操作方法复制到Collection集合类的原型对象中
// 这样就可以直接通过集合对象调用Underscore相关的集合方法
// 这些方法在调用时所操作的集合数据是当前Collection对象的models数据
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', 'indexOf', 'shuffle', 'lastIndexOf',
'isEmpty', 'chain'];
// Mix in each Underscore method as a proxy to `Collection#models`.
// 遍历已经定义的方法列表
_.each(methods, function(method) {
// 将方法复制到Collection集合类的原型对象
Collection.prototype[method] = function() {
// 调用时直接使用Underscore的方法, 上下文对象保持为Underscore对象
// 需要注意的是这里传递给Underscore方法的集合参数是 this.models, 因此在使用这些方法时, 所操作的集合对象是当前Collection对象的models数据
var args = slice.call(arguments);
args.unshift(this.models);
return _[method].apply(_, args);
};
});
// Underscore methods that take a property name as an argument.
// Underscore中以迭代器作为参数的方法
var attributeMethods = ['groupBy', 'countBy', 'sortBy'];
// Use attributes instead of properties.
_.each(attributeMethods, function(method) {
Collection.prototype[method] = function(value, context) {
// 获取当前的迭代函数
var iterator = _.isFunction(value) ? value : function(model) {
return model.get(value);
};
// 调用Underscore对应的方法来执行
return _[method](this.models, iterator, context);
};
});