【原创】backbone1.1.0源码解析之Collection

晚上躺在床上,继续完成对Backbone.Collection的源码解析。

首先讲讲它用来干嘛?

Backbone.Collection的实例表示一个集合,是很多model组成的,如果用model比喻成数据表中的row,那么collection就是那张数据表。

在mvc单页面程序里面,我们不可能只用一条一条的数据,我们更需要多条数据的处理,并且能够统一的管理这多条数据,无论是网络请求还是前端交互。

就好比前端有一个datagrid,很多row的数据,可以抽象成一个collection,row和collection之间就存在这联系,如果每一个row发生了变化,那么collection就

需要知道这个变化,而且根据这个变化做出相应的处理,就比如说添加,删除,更新等操作。

 

好了,话不多说,还是给上源码注释把,之后会给出一些demo,来具体演示一下

 

【原创】backbone1.1.0源码解析之Collection
  1   // Backbone.Collection
  2   // -------------------
  3 
  4   // If models tend to represent a single row of data, a Backbone Collection is
  5   // more analagous to a table full of data ... or a small slice or page of that
  6   // table, or a collection of rows that belong together for a particular reason
  7   // -- all of the messages in this particular folder, all of the documents
  8   // belonging to this particular author, and so on. Collections maintain
  9   // indexes of their models, both in order, and for lookup by `id`.
 10 
 11   // Create a new **Collection**, perhaps to contain a specific type of `model`.
 12   // If a `comparator` is specified, the Collection will maintain
 13   // its models in sort order, as they‘re added and removed.
 14   // 既然Model实例是数据库中,某一个数据表的,某一行数据,
 15   // 那么Collection实例就可以理解为这个数据库中的,这个数据表,这张数据表每一行的数据格式都是一样的
 16   var Collection = Backbone.Collection = function(models, options) {
 17 
 18     // 代码兼容
 19     options || (options = {});
 20 
 21     // 初始化Collection实例时,如果设置了options.model,用options.model代替this.model
 22     // this.model默认值是Backbone.Model,也可以是用户继承Backbone.Collection时可以覆盖重写,
 23     // 显然this.model是一个构造函数,是构造Collection实例中的每一个Model元素的构造函数,
 24     // 它的用途在于如果参数models数组里面的传递的不是Model实例,而是一些属性列表(hash),那么就可以
 25     // 将这些属性列表传入this.model构造函数来创建model实例
 26     if (options.model) this.model = options.model;
 27 
 28     // comparator用来对对集合元素进行排序,可以使函数,也可以是某个属性名
 29     if (options.comparator !== void 0) this.comparator = options.comparator;
 30 
 31     // 初始化对象的一些属性
 32     this._reset();
 33 
 34     // 调用initialize函数用于初始化,一般initialize用于用户自定义
 35     this.initialize.apply(this, arguments);
 36 
 37     // 如果构造时,传递给Collection实例models,那么向Collection实例中添加这些models
 38     // {silent: true} 表示不触发reset事件
 39     // 个人觉得这里用 this.add(models, _.extend({silent: true}, options))也可以
 40     // 可能主要是一个严谨问题,reset方法内部会对做清理工作
 41     // 因为有可能用户会这样使用:
 42     // Backbone.Collection.apply(CollectionObj, models, options);
 43     // CollectionObj为已经初始化的Collection实例并且已经包含多个models
 44     if (models) this.reset(models, _.extend({silent: true}, options));
 45   };
 46 
 47   // Default options for `Collection#set`.
 48   // 这里是一些配置,为了方便使用
 49   // 分别是set操作和add操作的默认配置
 50   // 显然是默认情况下:
 51   // set操作:add新model,remove旧model,merge旧model
 52   // add操作:add新model
 53   // 要想打破这个束缚,只要在调用set和add方法时,传递对应的options参数即可
 54   var setOptions = {add: true, remove: true, merge: true};
 55   var addOptions = {add: true, remove: false};
 56 
 57   // Define the Collection‘s inheritable methods.
 58   // 定义一些Collection的原型方法
 59   _.extend(Collection.prototype, Events, {
 60 
 61     // The default model for a collection is just a **Backbone.Model**.
 62     // This should be overridden in most cases.
 63     // Collection实例里元素的构造函数
 64     // 简单点说就是Collection实例的models都是由Backbone.Model构造出来的
 65     // 显然这个值大部分情况下需要被覆盖重写,有两种方法
 66     // 1. new Backbone.Collection(models,{model:CustomModel});
 67     // 2. CustomCollection = Backbone.extend({model:CustomModel});
 68     model: Model,
 69 
 70     // Initialize is an empty function by default. Override it with your own
 71     // initialization logic.
 72     // 初始化函数,用户根据需求自定义重写
 73     initialize: function(){},
 74 
 75     // The JSON representation of a Collection is an array of the
 76     // models‘ attributes.
 77     // 以数组格式鲜明的显示集合数据
 78     toJSON: function(options) {
 79       return this.map(function(model){ return model.toJSON(options); });
 80     },
 81 
 82     // Proxy `Backbone.sync` by default.
 83     // ajax接口,用于异步获取数据,可以根据需求覆盖重写
 84     sync: function() {
 85       return Backbone.sync.apply(this, arguments);
 86     },
 87 
 88     // Add a model, or list of models to the set.
 89     // 向集合中添加一个model或多个model(数组)
 90     add: function(models, options) {
 91       return this.set(models, _.extend({merge: false}, options, addOptions));
 92     },
 93 
 94     // Remove a model, or a list of models from the set.
 95     // 从集合中删除一个model或多个model(数组)
 96     // 注意这里的每一个model可以是model实例,model.id或者model.cid
 97     // Collection的get方法会作处理
 98     remove: function(models, options) {
 99 
100       // 统一转换成数组处理
101       var singular = !_.isArray(models);
102       
103       models = singular ? [models] : _.clone(models);
104       options || (options = {});
105 
106       var i, l, index, model;
107 
108       // 遍历删除
109       for (i = 0, l = models.length; i < l; i++) {
110 
111         // 从集合中获取指定的model
112         model = models[i] = this.get(models[i]);
113 
114         // 如果model不存在,那么continue
115         if (!model) continue;
116 
117         // 否则删除该model在集合中的相关信息
118         // 从集合里删除该model
119         delete this._byId[model.id];
120         delete this._byId[model.cid];
121         index = this.indexOf(model);
122         this.models.splice(index, 1);
123         this.length--;
124 
125         // 触发remove事件
126         if (!options.silent) {
127 
128           // 删除时该model的索引,通过options传递给callback
129           options.index = index;
130           model.trigger(‘remove‘, model, this, options);
131         }
132 
133         // 删除该model与该集合的联系
134         // 因为可能删除的这个model可能被用于其他Collection实例
135         this._removeReference(model);
136       }
137 
138       // 返回删除后的model(集合)
139       return singular ? models[0] : models;
140     },
141 
142     // Update a collection by `set`-ing a new list of models, adding new ones,
143     // removing models that are no longer present, and merging models that
144     // already exist in the collection, as necessary. Similar to **Model#set**,
145     // the core operation for updating the data contained by the collection.
146     set: function(models, options) {
147 
148       // 配置选项,默认值是setOptions
149       options = _.defaults({}, options, setOptions);
150 
151       // 解析或者过滤,并返回指定格式数据
152       if (options.parse) models = this.parse(models, options);
153 
154       // 统一转换成数组处理
155       var singular = !_.isArray(models);
156       models = singular ? (models ? [models] : []) : _.clone(models);
157 
158       var i, l, id, model, attrs, existing, sort;
159 
160       // at表示model索引
161       // targetModel表示集合元素的类型
162       var at = options.at;
163       var targetModel = this.model;
164 
165       // 是否排序
166       // 注意:如果指定了at,那么排序就无意义
167       // 也可以手动通过设置options.sort来阻止排序
168       var sortable = this.comparator && (at == null) && options.sort !== false;
169 
170       // 按某个属性值排序
171       var sortAttr = _.isString(this.comparator) ? this.comparator : null;
172 
173       var toAdd = [], toRemove = [], modelMap = {};
174       var add = options.add, merge = options.merge, remove = options.remove;
175 
176       // (!sortable && add && remove) ? [] : false
177       // 这里的order有什么用?
178       // 想了半天我的理解为:
179       // 在不需要进行排序的的大前提下,且add和remove都为true时,
180       // 使用order数组有序存储修正过的models,最后使用将集合的元素清空,
181       // 直接添加order数组到集合中,可以保证集合中元素的顺序和添加时元素的顺序一致
182       // 注意:只有add和remove为true时,后面才可以先进行清空操作(this.models.length = 0;)
183       //      然后再在最后添加order数组,从而保证元素的个数和顺序都正确无误
184       var order = !sortable && add && remove ? [] : false;
185 
186       // Turn bare objects into model references, and prevent invalid models
187       // from being added.
188       // 遍历处理models或者属性列表(hash)
189       for (i = 0, l = models.length; i < l; i++) {
190 
191         attrs = models[i];
192 
193         // 如果attrs是Model实例(这里有可能是子类实例)
194         // 注意这里model被负值,接下来可能作为判断条件
195         if (attrs instanceof Model) {
196           id = model = attrs;
197 
198         // 如果是属性列表(hash)根据idAttribute获取id
199         } else {
200           id = attrs[targetModel.prototype.idAttribute];
201         }
202 
203         // If a duplicate is found, prevent it from being added and
204         // optionally merge it into the existing model.
205         // 通过id进行查找当前集合是否存在该model
206         // 这里的id显然可以是model实例,也可以id字符串
207 
208         // 如果该model已存在
209         if (existing = this.get(id)) {
210 
211           // 通过保存cid,为下面remove操作删除model做准备
212           if (remove) modelMap[existing.cid] = true;
213 
214           // 如果合并则...执行接下来...
215           // 如果不合并,那么将被忽略
216           if (merge) {
217 
218             // 获取属性列表
219             attrs = attrs === model ? model.attributes : attrs;
220 
221             // 过滤属性列表
222             if (options.parse) attrs = existing.parse(attrs, options);
223 
224             // 设置属性列表
225             existing.set(attrs, options);
226 
227             // sort是后面用来标识是否排序
228             // 条件判断:
229             // sortable表示集合设置了排序
230             // !sort表示还没有被设置
231             // existing.hasChanged(sortAttr)表示model的排序属性有变化过
232             if (sortable && !sort && existing.hasChanged(sortAttr)) sort = true;
233           }
234 
235           // 重新负值修改过的model
236           models[i] = existing;
237 
238         // If this is a new, valid model, push it to the `toAdd` list.
239         // 如果该model不存在,且add
240         } else if (add) {
241 
242           // 格式化attrs,统一转换为该符合该集合的model实例
243           model = models[i] = this._prepareModel(attrs, options);
244 
245           // model为false,表示转换失败
246           if (!model) continue;
247 
248           // 添加到toAdd列表中,等候处理
249           toAdd.push(model);
250 
251           // Listen to added models‘ events, and index models for lookup by
252           // `id` and by `cid`.
253           // 给集合中的每个model都添加all事件
254           // 这样的话只要model发生任何事件都会通知集合,进而可以进行相关操作
255           model.on(‘all‘, this._onModelEvent, this);
256 
257           // 设置索引,方便获取某个model
258           this._byId[model.cid] = model;
259 
260           // 如果服务端数据有返回唯一键
261           if (model.id != null) this._byId[model.id] = model;
262         }
263 
264 
265         if (order) order.push(existing || model);
266       }
267 
268       // Remove nonexistent models if appropriate.
269       // 删除model
270       if (remove) {
271 
272         // 遍历集合,通过上面搜集的modelMap进行用cid映射删除对应的model
273         for (i = 0, l = this.length; i < l; ++i) {
274           if (!modelMap[(model = this.models[i]).cid]) toRemove.push(model);
275         }
276         if (toRemove.length) this.remove(toRemove, options);
277       }
278 
279       // See if sorting is needed, update `length` and splice in new models.
280       // 添加model
281       if (toAdd.length || (order && order.length)) {
282 
283         if (sortable) sort = true;
284 
285         this.length += toAdd.length;
286 
287         // 添加model到指定位置
288         if (at != null) {
289           for (i = 0, l = toAdd.length; i < l; i++) {
290             this.models.splice(at + i, 0, toAdd[i]);
291           }
292 
293         // 直接通过push添加model
294         } else {
295 
296           // 如果order存在,那么先进行清空操作,再添加整个order
297           if (order) this.models.length = 0;
298 
299           var orderedModels = order || toAdd;
300           for (i = 0, l = orderedModels.length; i < l; i++) {
301             this.models.push(orderedModels[i]);
302           }
303         }
304       }
305 
306       // Silently sort the collection if appropriate.
307       // 排序
308       if (sort) this.sort({silent: true});
309 
310       // Unless silenced, it‘s time to fire all appropriate add/sort events.
311       // 触发每一个model的add事件
312       if (!options.silent) {
313         for (i = 0, l = toAdd.length; i < l; i++) {
314           (model = toAdd[i]).trigger(‘add‘, model, this, options);
315         }
316         // 如果排序或者order存在,那么触发sort事件
317         // 注意:order存在表示用户没有定义comparator等,那么就可以通过sort事件
318         //      对添加进来的models做个排序处理等
319         if (sort || (order && order.length)) this.trigger(‘sort‘, this, options);
320       }
321       
322       // Return the added (or merged) model (or models).
323       // 返回修改添加过的model(集合)
324       return singular ? models[0] : models;
325     },
326 
327     // When you have more items than you want to add or remove individually,
328     // you can reset the entire set with a new list of models, without firing
329     // any granular `add` or `remove` events. Fires `reset` when finished.
330     // Useful for bulk operations and optimizations.
331     // 用新一批的models重置当前的集合,也就是说集合中已存在的旧models会全被删除掉
332     // 这样的好处在于不用一个个去删除,再去添加
333     reset: function(models, options) {
334 
335       options || (options = {});
336 
337       // 删除旧models与集合的联系
338       for (var i = 0, l = this.models.length; i < l; i++) {
339         this._removeReference(this.models[i]);
340       }
341 
342       // 将旧models通过options对象传递给reset事件触发的callback
343       options.previousModels = this.models;
344 
345       // 清空操作
346       this._reset();
347 
348       // 调用add方法,添加新一批models
349       models = this.add(models, _.extend({silent: true}, options));
350 
351       // 触发集合的reset事件
352       if (!options.silent) this.trigger(‘reset‘, this, options);
353 
354       return models;
355     },
356 
357     // Add a model to the end of the collection.
358     // 添加model到集合的尾部(利用at参数)
359     // 当然这里的model也可以是一个数组(添加后保持原有的顺序)
360     push: function(model, options) {
361       return this.add(model, _.extend({at: this.length}, options));
362     },
363 
364     // Remove a model from the end of the collection.
365     // 删除集合最后一个model
366     pop: function(options) {
367       var model = this.at(this.length - 1);
368       this.remove(model, options);
369       return model;
370     },
371 
372     // Add a model to the beginning of the collection.
373     // 添加model到集合的头部(利用at参数)
374     // 当然这里的model也可以是一个数组(添加后保持原有的顺序)
375     unshift: function(model, options) {
376       return this.add(model, _.extend({at: 0}, options));
377     },
378 
379     // Remove a model from the beginning of the collection.
380     // 删除集合第一个model
381     shift: function(options) {
382       var model = this.at(0);
383       this.remove(model, options);
384       return model;
385     },
386 
387     // Slice out a sub-array of models from the collection.
388     // 因为this.models是一个数组,可以才用slice方法进行截取
389     slice: function() {
390       return slice.apply(this.models, arguments);
391     },
392 
393     // Get a model from the set by id.
394     // 获取集合中的model
395     // 参数obj可以是model实例,cid,id
396     get: function(obj) {
397       if (obj == null) return void 0;
398       return this._byId[obj.id] || this._byId[obj.cid] || this._byId[obj];
399     },
400 
401     // Get the model at the given index.
402     // 返回集合中指定索引的model
403     at: function(index) {
404       return this.models[index];
405     },
406 
407     // Return models with matching attributes. Useful for simple cases of
408     // `filter`.
409     // 返回匹配attrs属性列表的model(集合)
410     // first表示只返回匹配结果的第一个
411     where: function(attrs, first) {
412       if (_.isEmpty(attrs)) return first ? void 0 : [];
413       return this[first ? ‘find‘ : ‘filter‘](function(model) {
414         for (var key in attrs) {
415           if (attrs[key] !== model.get(key)) return false;
416         }
417         return true;
418       });
419     },
420 
421     // Return the first model with matching attributes. Useful for simple cases
422     // of `find`.
423     // 返回第一个匹配attrs属性列表的结果
424     findWhere: function(attrs) {
425       return this.where(attrs, true);
426     },
427 
428     // Force the collection to re-sort itself. You don‘t need to call this under
429     // normal circumstances, as the set will maintain sort order as each item
430     // is added.
431     // 给集合中的models排序
432     sort: function(options) {
433 
434       // 根据comparator排序
435       if (!this.comparator) throw new Error(‘Cannot sort a set without a comparator‘);
436       options || (options = {});
437 
438       // Run sort based on type of `comparator`.
439       // 如果comparator是字符串(属性名),或者参数为1的函数,用underscore的sortBy函数排序
440       if (_.isString(this.comparator) || this.comparator.length === 1) {
441         this.models = this.sortBy(this.comparator, this);
442 
443       // 负责数组自带的sort函数排序
444       } else {
445         this.models.sort(_.bind(this.comparator, this));
446       }
447 
448       // 触发sort事件
449       if (!options.silent) this.trigger(‘sort‘, this, options);
450       return this;
451     },
452 
453     // Pluck an attribute from each model in the collection.
454     // 返回由所有models的某个属性组成的数组
455     pluck: function(attr) {
456       return _.invoke(this.models, ‘get‘, attr);
457     },
458 
459     // Fetch the default set of models for this collection, resetting the
460     // collection when they arrive. If `reset: true` is passed, the response
461     // data will be passed through the `reset` method instead of `set`.
462     // 从服务器端获取数据填充集合
463     fetch: function(options) {
464 
465       options = options ? _.clone(options) : {};
466 
467       // 服务器端过来的数据需要parse一下,传递正确格式的数据(models)
468       if (options.parse === void 0) options.parse = true;
469 
470       var success = options.success;
471       var collection = this;
472 
473       // 成功回调
474       options.success = function(resp) {
475 
476         // 服务器端获取过来的数据,集合采取reset还是set方式处理数据?
477         var method = options.reset ? ‘reset‘ : ‘set‘;
478         collection[method](resp, options);
479 
480         // 用户自定义回调
481         if (success) success(collection, resp, options);
482 
483         // 触发sync事件
484         collection.trigger(‘sync‘, collection, resp, options);
485       };
486 
487       // 失败回调的包装
488       wrapError(this, options);
489 
490       // 开始读取数据
491       return this.sync(‘read‘, this, options);
492     },
493 
494     // Create a new instance of a model in this collection. Add the model to the
495     // collection immediately, unless `wait: true` is passed, in which case we
496     // wait for the server to agree.
497     // 创建model实例,然后添加到集合中,最后存储到服务器端
498     create: function(model, options) {
499 
500       options = options ? _.clone(options) : {};
501 
502       // 转换为model实例
503       if (!(model = this._prepareModel(model, options))) return false;
504 
505       // wait参数,表示是否先在前端先展示
506       if (!options.wait) this.add(model, options);
507 
508       var collection = this;
509       var success = options.success;
510 
511       // 成功回调
512       options.success = function(model, resp, options) {
513         if (options.wait) collection.add(model, options);
514         if (success) success(model, resp, options);
515       };
516 
517       // 保存到服务端
518       model.save(null, options);
519       return model;
520     },
521 
522     // **parse** converts a response into a list of models to be added to the
523     // collection. The default implementation is just to pass it through.
524     // 过滤解析当前models或是从服务端返回的数据
525     parse: function(resp, options) {
526       return resp;
527     },
528 
529     // Create a new collection with an identical list of models as this one.
530     // 克隆当前集合
531     clone: function() {
532       return new this.constructor(this.models);
533     },
534 
535     // Private method to reset all internal state. Called when the collection
536     // is first initialized or reset.
537     // 重置Collection实例的一些内部变量(状态)
538     _reset: function() {
539       this.length = 0;
540       this.models = [];
541       this._byId  = {};
542     },
543 
544     // Prepare a hash of attributes (or other model) to be added to this
545     // collection.
546     // 前面说过,初始化时的models,也就是这里的attrs可以时属性列表(hash)
547     // 这里还可以是其他model,那么需要统一处理成该集合应该包含的model实例
548     _prepareModel: function(attrs, options) {
549 
550       // 如果是model实例
551       if (attrs instanceof Model) {
552 
553         // 设置collection,与集合建立联系
554         if (!attrs.collection) attrs.collection = this;
555         return attrs;
556       }
557 
558       // 如果是属性列表
559       options = options ? _.clone(options) : {};
560 
561       // 设置与集合的联系
562       options.collection = this;
563 
564       // 创建model实例
565       var model = new this.model(attrs, options);
566 
567       if (!model.validationError) return model;
568 
569       // 创建失败,触发集合的invalida事件,并返回false
570       this.trigger(‘invalid‘, this, model.validationError, options);
571       return false;
572     },
573 
574     // Internal method to sever a model‘s ties to a collection.
575     // 删除某个model与集合的联系
576     _removeReference: function(model) {
577       if (this === model.collection) delete model.collection;
578       model.off(‘all‘, this._onModelEvent, this);
579     },
580 
581     // Internal method called every time a model in the set fires an event.
582     // Sets need to update their indexes when models change ids. All other
583     // events simply proxy through. "add" and "remove" events that originate
584     // in other collections are ignored.
585     // 因为在向集合中添加model实例时,会给每一个model实例绑定一个all事件,
586     // 对应的回调函数就是_onModelEvent,也就是说,每个model的(属性)变化都会
587     // 触发该函数,该函数只要根据event是什么,做出相应的处理就可以了
588     _onModelEvent: function(event, model, collection, options) {
589 
590       // 如果collection不是集合本身,过滤掉
591       if ((event === ‘add‘ || event === ‘remove‘) && collection !== this) return;
592 
593       // model被destory后,从集合中remove掉即可
594       if (event === ‘destroy‘) this.remove(model, options);
595 
596       // 如果model的唯一键(服务器端)发生变化,需要修改this._byId的映射
597       if (model && event === ‘change:‘ + model.idAttribute) {
598         delete this._byId[model.previous(model.idAttribute)];
599         if (model.id != null) this._byId[model.id] = model;
600       }
601 
602       // 触发集合对应的事件
603       this.trigger.apply(this, arguments);
604     }
605 
606   });
607 
608   // Underscore methods that we want to implement on the Collection.
609   // 90% of the core usefulness of Backbone Collections is actually implemented
610   // right here:
611   var methods = [‘forEach‘, ‘each‘, ‘map‘, ‘collect‘, ‘reduce‘, ‘foldl‘,
612     ‘inject‘, ‘reduceRight‘, ‘foldr‘, ‘find‘, ‘detect‘, ‘filter‘, ‘select‘,
613     ‘reject‘, ‘every‘, ‘all‘, ‘some‘, ‘any‘, ‘include‘, ‘contains‘, ‘invoke‘,
614     ‘max‘, ‘min‘, ‘toArray‘, ‘size‘, ‘first‘, ‘head‘, ‘take‘, ‘initial‘, ‘rest‘,
615     ‘tail‘, ‘drop‘, ‘last‘, ‘without‘, ‘difference‘, ‘indexOf‘, ‘shuffle‘,
616     ‘lastIndexOf‘, ‘isEmpty‘, ‘chain‘];
617 
618   // Mix in each Underscore method as a proxy to `Collection#models`.
619   // 因为集合的models是一个数组,那么underscore的一系列方法都可以附加到Collection的原型上,
620   // 可以方便处理集合
621   _.each(methods, function(method) {
622     Collection.prototype[method] = function() {
623       var args = slice.call(arguments);
624       args.unshift(this.models);
625       return _[method].apply(_, args);
626     };
627   });
628 
629   // Underscore methods that take a property name as an argument.
630   var attributeMethods = [‘groupBy‘, ‘countBy‘, ‘sortBy‘];
631 
632   // Use attributes instead of properties.
633   // 同样是间接利用underscore的方法,只是做了一些简单处理
634   _.each(attributeMethods, function(method) {
635     Collection.prototype[method] = function(value, context) {
636       var iterator = _.isFunction(value) ? value : function(model) {
637         return model.get(value);
638       };
639       return _[method](this.models, iterator, context);
640     };
641   });
【原创】backbone1.1.0源码解析之Collection

好了,各位晚安,明天又是周五了,开心~~

【原创】backbone1.1.0源码解析之Collection

上一篇:MySQL Workbench 6 不能删除数据等问题(“Error Code: 1175”) 和入门教程


下一篇:深入理解计算机系统--信号