Backbone中主要的业务逻辑位于Model和Collection,上一篇介绍了Backbone中的Model,这篇文章中将主要探讨Collection的源码。
让我们先来看一下Collection的构造函数:
// 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. var Collection = Backbone.Collection = function(models, options) { options || (options = {}); if (options.model) this.model = options.model;//model对象指定collection管理的Model类型 //comparator 排序使用 if (options.comparator !== void 0) this.comparator = options.comparator; this._reset();// 重置集合 this.initialize.apply(this, arguments);//调用初始化方法 //如果参数中传递model数组,则利用models数组重置集合 if (models) this.reset(models, _.extend({silent: true}, options)); };
接下来是在set
, add
, remove
中经常用到的splice
函数:
// Splices `insert` into `array` at index `at`. //将insert数组在at位置拼接到array中 var splice = function(array, insert, at) { //防止at超出数组长度 at = Math.min(Math.max(at, 0), array.length); //先创建一个空数组 var tail = Array(array.length - at); var length = insert.length; var i; //将at之后的数组元素暂存到tail中 for (i = 0; i < tail.length; i++) tail[i] = array[i + at]; //用insert替换array中at之后的元素 for (i = 0; i < length; i++) array[i + at] = insert[i]; //将原at之后的array元素放到insert元素之后 for (i = 0; i < tail.length; i++) array[i + length + at] = tail[i]; };
下面我们来讲解set
函数,set
函数是Collection中非常重要的一个函数,集增删改查与一身,处理了大量的核心业务逻辑:
// 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) { if (models == null) return; // var setOptions = {add: true, remove: true, merge: true}; options = _.extend({}, setOptions, options);//setOptions是预设参数 // 如果models为原生对象,会利用Collection中model属性来转化成Model实例 if (options.parse && !this._isModel(models)) { models = this.parse(models, options) || []; } var singular = !_.isArray(models); models = singular ? [models] : models.slice(); // 处理at,确保at为合理的数字 var at = options.at; if (at != null) at = +at; //转化为数字 if (at > this.length) at = this.length; if (at < 0) at += this.length + 1; var set = [];// set表示经过本次处理后应当存在于this.models中的model var toAdd = [];// 本次操作增加的model数组 var toMerge = [];// 本次操后修改的model数组 var toRemove = [];// 本次操作删除掉的models var modelMap = {};//modelMap是本次变化后的应该存在于Collection中的models的key集合 var add = options.add; var merge = options.merge; var remove = options.remove; var sort = false; //有comparator属性,没设置at,sort为true //如果对collection做了插入的话,需要自己手动排序 var sortable = this.comparator && at == null && options.sort !== false; //comparator 是model中的属性 var sortAttr = _.isString(this.comparator) ? this.comparator : null; // Turn bare objects into model references, and prevent invalid models // from being added. var model, i; //先过滤一遍,找出存在于collection中的和不存在于当前collection中的 for (i = 0; i < models.length; i++) {//处理add和existing model = models[i]; // If a duplicate is found, prevent it from being added and // optionally merge it into the existing model. // get根据idAttribute || cid 来查找 var existing = this.get(model); if (existing) { if (merge && model !== existing) { var attrs = this._isModel(model) ? model.attributes : model; if (options.parse) attrs = existing.parse(attrs, options);//在这之前应该验证一下 // 使用当前属性替换model中已存在属性 existing.set(attrs, options); toMerge.push(existing); // 查看排序字段是否有更改 if (sortable && !sort) sort = existing.hasChanged(sortAttr); } // 将更的model id存到modelMap中 if (!modelMap[existing.cid]) { modelMap[existing.cid] = true; set.push(existing); } models[i] = existing; // If this is a new, valid model, push it to the `toAdd` list. } else if (add) { // _prepareModel将原始对象转化为Model实例 model = models[i] = this._prepareModel(model, options); if (model) { toAdd.push(model); // _addReference 将model加入到Collection的_byId中,并绑定model的所有事件 this._addReference(model, options); modelMap[model.cid] = true; set.push(model); } } } // Remove stale models. if (remove) { for (i = 0; i < this.length; i++) { model = this.models[i]; // 在this.models中但不在本次set中的model,都要删除 if (!modelMap[model.cid]) toRemove.push(model); } //移除model,this.models、this._byId;移除model的绑定事件 if (toRemove.length) this._removeModels(toRemove, options); } // See if sorting is needed, update `length` and splice in new models. var orderChanged = false; var replace = !sortable && add && remove; if (set.length && replace) {//如果同时有加减操作,便将models放到this.models中 //如果this.models中set的数据不一致,则认为order有变化。 orderChanged = this.length !== set.length || _.some(this.models, function(m, index) { return m !== set[index]; });//没有启用排序,但是this.models与set不一致时,仍会触发sort事件 //处理完remove后,该删除的都删除掉;用set替换this.models this.models.length = 0; splice(this.models, set, 0); this.length = this.models.length; } else if (toAdd.length) {//如果仅仅是增加model,则将toAdd插入到指定位置去 if (sortable) sort = true; splice(this.models, toAdd, at == null ? this.length : at); this.length = this.models.length; } // Silently sort the collection if appropriate. //这里排序一下,但不要触发事件,在下文统一处理 if (sort) this.sort({silent: true}); // Unless silenced, it's time to fire all appropriate add/sort/update events. //collection跟新完毕后,再发送事件 // remove在上文删除时已触发 if (!options.silent) { for (i = 0; i < toAdd.length; i++) { if (at != null) options.index = at + i; model = toAdd[i]; model.trigger('add', model, this, options); } if (sort || orderChanged) this.trigger('sort', this, options); if (toAdd.length || toRemove.length || toMerge.length) { options.changes = { added: toAdd, removed: toRemove, merged: toMerge }; this.trigger('update', this, options); } } // Return the added (or merged) model (or models). return singular ? models[0] : models; },
Collection中存储model的属性有两个:this.models
数组和_byId
键值对,所有的增删改查都需要对这两个属性进行更新。
- 将models参数处理成
Model
的实例数组 - 处理options中的at参数,将其变成一个合理的数字
- 声明变量
set
,toAdd
,toMerge
,toRemove
,modelMap
- 遍历models参数,找出其中应当更改或者加入到Collection中的model并添加到上文的变量中
- 从Collection的
this.models
中删除不存在本次set中的model - 统一更改Collection中的
this.models
数组 - 排序,但不要触发sort事件
- 统一处理事件: add、sort、update事件
像add
函数内部就是利用set
函数来处理的:
// Add a model, or list of models to the set. `models` may be Backbone // Models or raw JavaScript objects to be converted to Models, or any // combination of the two. add: function(models, options) {//没有的会加进去,已存在的会根据options决定是否merge return this.set(models, _.extend({merge: false}, options, addOptions)); },
继add
之后,另一个重要的操作就是remove,Collection中的remove逻辑由remove
, _removeModels
, _removeReference
三个函数完成
remove: function(models, options) { //处理参数,models处理成数组 options = _.extend({}, options); var singular = !_.isArray(models); models = singular ? [models] : models.slice(); //删除掉models,并触发removed事件 var removed = this._removeModels(models, options); //从Collection层面上触发update事件 if (!options.silent && removed.length) { options.changes = {added: [], merged: [], removed: removed}; this.trigger('update', this, options);//触发update事件,注意options里面的change,这样可以方便很多事 } return singular ? removed[0] : removed;//注意api的返回值和事件参数的设置 },
// Internal method called by both remove and set. _removeModels: function(models, options) { var removed = []; for (var i = 0; i < models.length; i++) { var model = this.get(models[i]); if (!model) continue; // 首先从this.models数组中删除model var index = this.indexOf(model); this.models.splice(index, 1); this.length--; // Remove references before triggering 'remove' event to prevent an // infinite loop. #3693 // 从_byId中删除model的引用 delete this._byId[model.cid]; var id = this.modelId(model.attributes); if (id != null) delete this._byId[id]; // 触发Collection的remove事件 if (!options.silent) { options.index = index; model.trigger('remove', model, this, options); //被删除的model也触发remove事件 } // 删除model的引用和移除model的绑定事件 removed.push(model); this._removeReference(model, options); } return removed; },
// Internal method to sever a model's ties to a collection. _removeReference: function(model, options) { //断开model与collection的关联 delete this._byId[model.cid]; var id = this.modelId(model.attributes); if (id != null) delete this._byId[id]; if (this === model.collection) delete model.collection; // 移除所有的绑定事件 model.off('all', this._onModelEvent, this); },
remove整体逻辑如下:
- 处理models参数
- 从
this.models
与_byId
中删除model,触发Collection的remove事件 - 断开model与Collection的关联,移除model的绑定事件
- 触发Collection的update事件
与_removeReference
对应的是_addReference
,它的作用于_removeRenence
相反:
// Internal method to create a model's ties to a collection. _addReference: function(model, options) { //将model与collection关联起来,绑定model的各种事件 this._byId[model.cid] = model; var id = this.modelId(model.attributes); if (id != null) this._byId[id] = model; model.on('all', this._onModelEvent, this); },
最后要提一下的是reset函数:
// 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. reset: function(models, options) {//reset中不会触发add和remove事件 options = options ? _.clone(options) : {}; for (var i = 0; i < this.models.length; i++) { //断开与Collection的链接,和移除model的事件 this._removeReference(this.models[i], options); } options.previousModels = this.models;//多看设计 this._reset();//将Collection置空,length、this.models、this._byId models = this.add(models, _.extend({silent: true}, options)); // 触发事件 if (!options.silent) this.trigger('reset', this, options); return models; },