Backbone.js源码分析系列之Collection模块
来源:互联网 发布:侧卧按键开关封装淘宝 编辑:程序博客网 时间:2024/06/05 00:10
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); }; });
0 0
- Backbone.js源码分析系列之Collection模块
- Backbone.js源码分析系列之Events模块
- Backbone.js源码分析系列之Model模块
- 【转】backbone.js 0.9.2源码分析
- Backbone.js 0.9.2 源码分析收藏
- Backbone.js源码分析(珍藏版)
- Backbone系列:Collection的学习
- Backbone.js系列一 - Backbone.js初探
- jdk源码分析之Collection
- backbone源码分析
- Backbone TodoMVC 源码分析
- Backbone TodoMVC 源码分析
- Backbone.js系列教程二:Backbone.js深入解析之基础要求
- Backbone源码分析Backbone架构+流程图
- Backbone源码分析-Backbone架构+流程图
- Backbone源码分析-Backbone架构+流程图
- Backbone源码分析-Backbone架构+流程图
- Backbone源码分析-Backbone架构+流程图
- poj 2595 Treasure Exploration
- Codeforces Round #260 (Div. 2) B. Fedya and Maths
- 区别之守护进程 孤儿进程 僵尸进程
- jmeter使用2-springmvc request
- 25465413214654
- Backbone.js源码分析系列之Collection模块
- 黑马程序员--高新技术(内省)
- 是发个V大分还不
- Java中的Class类以及获取Class对象的三种方式
- Codeforces Round #260 (Div. 2) C. Boredom
- codeforces 455A Boredom dp
- FZU 1876 组合数学
- 在Linux中创建静态库.a和动态库.so
- jplogic 开发案例(FMS流媒体服务集成) 之 视频会议管理 (多人互动呼叫-应答模式视频会议含共享白板)