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

时间:2022-11-25 16:07:18

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

首先讲讲它用来干嘛?

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

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

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

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

 

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

 

  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 });

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