backbone.js 1.3.3----------------------------------------collection

collection就是model的集合,这里提供各种操作集合的方法。可以参照api。

fetch方法成功后,有两个回调函数可供使用,reset和set。

  1 // Backbone.Collection
  2   // -------------------
  3 
  4   // If models tend to represent a single row of data, a Backbone Collection is
  5   // more analogous 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   var Collection = Backbone.Collection = function(models, options) {
 15     options || (options = {});
 16     this.preinitialize.apply(this, arguments);
 17     if (options.model) this.model = options.model;
 18     if (options.comparator !== void 0) this.comparator = options.comparator;
 19     this._reset();
 20     this.initialize.apply(this, arguments);
 21     if (models) this.reset(models, _.extend({silent: true}, options));
 22   };
 23 
 24   // Default options for `Collection#set`.
 25   var setOptions = {add: true, remove: true, merge: true};
 26   var addOptions = {add: true, remove: false};
 27 
 28   // Splices `insert` into `array` at index `at`.
 29   var splice = function(array, insert, at) {
 30     at = Math.min(Math.max(at, 0), array.length);
 31     var tail = Array(array.length - at);
 32     var length = insert.length;
 33     var i;
 34     for (i = 0; i < tail.length; i++) tail[i] = array[i + at];
 35     for (i = 0; i < length; i++) array[i + at] = insert[i];
 36     for (i = 0; i < tail.length; i++) array[i + length + at] = tail[i];
 37   };
 38 
 39   // Define the Collection's inheritable methods.
 40   _.extend(Collection.prototype, Events, {
 41 
 42     // The default model for a collection is just a **Backbone.Model**.
 43     // This should be overridden in most cases.
 44     model: Model,
 45 
 46 
 47     // preinitialize is an empty function by default. You can override it with a function
 48     // or object.  preinitialize will run before any instantiation logic is run in the Collection.
 49     preinitialize: function(){},
 50 
 51     // Initialize is an empty function by default. Override it with your own
 52     // initialization logic.
 53     initialize: function(){},
 54 
 55     // The JSON representation of a Collection is an array of the
 56     // models' attributes.
 57     toJSON: function(options) {
 58       return this.map(function(model) { return model.toJSON(options); });
 59     },
 60 
 61     // Proxy `Backbone.sync` by default.
 62     sync: function() {
 63       return Backbone.sync.apply(this, arguments);
 64     },
 65 
 66     // Add a model, or list of models to the set. `models` may be Backbone
 67     // Models or raw JavaScript objects to be converted to Models, or any
 68     // combination of the two.
 69     add: function(models, options) {
 70       return this.set(models, _.extend({merge: false}, options, addOptions));
 71     },
 72 
 73     // Remove a model, or a list of models from the set.
 74     remove: function(models, options) {
 75       options = _.extend({}, options);
 76       var singular = !_.isArray(models);
 77       models = singular ? [models] : models.slice();
 78       var removed = this._removeModels(models, options);
 79       if (!options.silent && removed.length) {
 80         options.changes = {added: [], merged: [], removed: removed};
 81         this.trigger('update', this, options);
 82       }
 83       return singular ? removed[0] : removed;
 84     },
 85 
 86     // Update a collection by `set`-ing a new list of models, adding new ones,
 87     // removing models that are no longer present, and merging models that
 88     // already exist in the collection, as necessary. Similar to **Model#set**,
 89     // the core operation for updating the data contained by the collection.
 90     // 1.解析这次的操作add,merge,remove,sort
 91     // 2.执行操作
 92     // 3.判断silent,执行事件
 93     set: function(models, options) {
 94       if (models == null) return;
 95 
 96       options = _.extend({}, setOptions, options);
 97       if (options.parse && !this._isModel(models)) {
 98         models = this.parse(models, options) || [];
 99       }
100 
101       var singular = !_.isArray(models);
102       models = singular ? [models] : models.slice();
103 
104       var at = options.at;
105       if (at != null) at = +at;
106       if (at > this.length) at = this.length;
107       if (at < 0) at += this.length + 1;
108 
109       var set = [];
110       var toAdd = [];
111       var toMerge = [];
112       var toRemove = [];
113       var modelMap = {};
114 
115       var add = options.add;
116       var merge = options.merge;
117       var remove = options.remove;
118 
119       var sort = false;
120       var sortable = this.comparator && at == null && options.sort !== false;
121       var sortAttr = _.isString(this.comparator) ? this.comparator : null;
122 
123       // Turn bare objects into model references, and prevent invalid models
124       // from being added.
125       var model, i;
126       for (i = 0; i < models.length; i++) {
127         model = models[i];
128 
129         // If a duplicate is found, prevent it from being added and
130         // optionally merge it into the existing model.
131         var existing = this.get(model);
132         if (existing) {
133           if (merge && model !== existing) {
134             var attrs = this._isModel(model) ? model.attributes : model;
135             if (options.parse) attrs = existing.parse(attrs, options);
136             existing.set(attrs, options);
137             toMerge.push(existing);
138             if (sortable && !sort) sort = existing.hasChanged(sortAttr);
139           }
140           if (!modelMap[existing.cid]) {
141             modelMap[existing.cid] = true;
142             set.push(existing);
143           }
144           models[i] = existing;
145 
146         // If this is a new, valid model, push it to the `toAdd` list.
147         } else if (add) {
148           model = models[i] = this._prepareModel(model, options);
149           if (model) {
150             toAdd.push(model);
151             this._addReference(model, options);
152             modelMap[model.cid] = true;
153             set.push(model);
154           }
155         }
156       }
157 
158       // Remove stale models.
159       if (remove) {
160         for (i = 0; i < this.length; i++) {
161           model = this.models[i];
162           if (!modelMap[model.cid]) toRemove.push(model);
163         }
164         if (toRemove.length) this._removeModels(toRemove, options);
165       }
166 
167       // See if sorting is needed, update `length` and splice in new models.
168       var orderChanged = false;
169       var replace = !sortable && add && remove;
170       if (set.length && replace) {
171         orderChanged = this.length !== set.length || _.some(this.models, function(m, index) {
172           return m !== set[index];
173         });
174         this.models.length = 0;
175         splice(this.models, set, 0);
176         this.length = this.models.length;
177       } else if (toAdd.length) {
178         if (sortable) sort = true;
179         splice(this.models, toAdd, at == null ? this.length : at);
180         this.length = this.models.length;
181       }
182 
183       // Silently sort the collection if appropriate.
184       if (sort) this.sort({silent: true});
185 
186       // Unless silenced, it's time to fire all appropriate add/sort/update events.
187       if (!options.silent) {
188         for (i = 0; i < toAdd.length; i++) {
189           if (at != null) options.index = at + i;
190           model = toAdd[i];
191           model.trigger('add', model, this, options);
192         }
193         if (sort || orderChanged) this.trigger('sort', this, options);
194         if (toAdd.length || toRemove.length || toMerge.length) {
195           options.changes = {
196             added: toAdd,
197             removed: toRemove,
198             merged: toMerge
199           };
200           this.trigger('update', this, options);
201         }
202       }
203 
204       // Return the added (or merged) model (or models).
205       return singular ? models[0] : models;
206     },
207 
208     // When you have more items than you want to add or remove individually,
209     // you can reset the entire set with a new list of models, without firing
210     // any granular `add` or `remove` events. Fires `reset` when finished.
211     // Useful for bulk operations and optimizations.
212     reset: function(models, options) {
213       options = options ? _.clone(options) : {};
214       for (var i = 0; i < this.models.length; i++) {
215         this._removeReference(this.models[i], options);
216       }
217       options.previousModels = this.models;
218       this._reset();
219       models = this.add(models, _.extend({silent: true}, options));
220       if (!options.silent) this.trigger('reset', this, options);
221       return models;
222     },
223 
224     // Add a model to the end of the collection.
225     push: function(model, options) {
226       return this.add(model, _.extend({at: this.length}, options));
227     },
228 
229     // Remove a model from the end of the collection.
230     pop: function(options) {
231       var model = this.at(this.length - 1);
232       return this.remove(model, options);
233     },
234 
235     // Add a model to the beginning of the collection.
236     unshift: function(model, options) {
237       return this.add(model, _.extend({at: 0}, options));
238     },
239 
240     // Remove a model from the beginning of the collection.
241     shift: function(options) {
242       var model = this.at(0);
243       return this.remove(model, options);
244     },
245 
246     // Slice out a sub-array of models from the collection.
247     slice: function() {
248       return slice.apply(this.models, arguments);
249     },
250 
251     // Get a model from the set by id, cid, model object with id or cid
252     // properties, or an attributes object that is transformed through modelId.
253     get: function(obj) {
254       if (obj == null) return void 0;
255       return this._byId[obj] ||
256         this._byId[this.modelId(obj.attributes || obj)] ||
257         obj.cid && this._byId[obj.cid];
258     },
259 
260     // Returns `true` if the model is in the collection.
261     has: function(obj) {
262       return this.get(obj) != null;
263     },
264 
265     // Get the model at the given index.
266     at: function(index) {
267       if (index < 0) index += this.length;
268       return this.models[index];
269     },
270 
271     // Return models with matching attributes. Useful for simple cases of
272     // `filter`.
273     where: function(attrs, first) {
274       return this[first ? 'find' : 'filter'](attrs);
275     },
276 
277     // Return the first model with matching attributes. Useful for simple cases
278     // of `find`.
279     findWhere: function(attrs) {
280       return this.where(attrs, true);
281     },
282 
283     // Force the collection to re-sort itself. You don't need to call this under
284     // normal circumstances, as the set will maintain sort order as each item
285     // is added.
286     sort: function(options) {
287       var comparator = this.comparator;
288       if (!comparator) throw new Error('Cannot sort a set without a comparator');
289       options || (options = {});
290 
291       var length = comparator.length;
292       if (_.isFunction(comparator)) comparator = _.bind(comparator, this);
293 
294       // Run sort based on type of `comparator`.
295       if (length === 1 || _.isString(comparator)) {
296         this.models = this.sortBy(comparator);
297       } else {
298         this.models.sort(comparator);
299       }
300       if (!options.silent) this.trigger('sort', this, options);
301       return this;
302     },
303 
304     // Pluck an attribute from each model in the collection.
305     pluck: function(attr) {
306       return this.map(attr + '');
307     },
308 
309     // Fetch the default set of models for this collection, resetting the
310     // collection when they arrive. If `reset: true` is passed, the response
311     // data will be passed through the `reset` method instead of `set`.
312     fetch: function(options) {
313       options = _.extend({parse: true}, options);
314       var success = options.success;
315       var collection = this;
316       options.success = function(resp) {
317         var method = options.reset ? 'reset' : 'set';
318         collection[method](resp, options);
319         if (success) success.call(options.context, collection, resp, options);
320         collection.trigger('sync', collection, resp, options);
321       };
322       wrapError(this, options);
323       return this.sync('read', this, options);
324     },
325 
326     // Create a new instance of a model in this collection. Add the model to the
327     // collection immediately, unless `wait: true` is passed, in which case we
328     // wait for the server to agree.
329     create: function(model, options) {
330       options = options ? _.clone(options) : {};
331       var wait = options.wait;
332       model = this._prepareModel(model, options);
333       if (!model) return false;
334       if (!wait) this.add(model, options);
335       var collection = this;
336       var success = options.success;
337       options.success = function(m, resp, callbackOpts) {
338         if (wait) collection.add(m, callbackOpts);
339         if (success) success.call(callbackOpts.context, m, resp, callbackOpts);
340       };
341       model.save(null, options);
342       return model;
343     },
344 
345     // **parse** converts a response into a list of models to be added to the
346     // collection. The default implementation is just to pass it through.
347     //可以复写该方法
348     parse: function(resp, options) {
349       return resp;
350     },
351 
352     // Create a new collection with an identical list of models as this one.
353     clone: function() {
354       return new this.constructor(this.models, {
355         model: this.model,
356         comparator: this.comparator
357       });
358     },
359 
360     // Define how to uniquely identify models in the collection.
361     modelId: function(attrs) {
362       return attrs[this.model.prototype.idAttribute || 'id'];
363     },
364 
365     // Private method to reset all internal state. Called when the collection
366     // is first initialized or reset.
367     _reset: function() {
368       this.length = 0;
369       this.models = [];
370       this._byId  = {};
371     },
372 
373     // Prepare a hash of attributes (or other model) to be added to this
374     // collection.
375     _prepareModel: function(attrs, options) {
376       if (this._isModel(attrs)) {
377         if (!attrs.collection) attrs.collection = this;
378         return attrs;
379       }
380       options = options ? _.clone(options) : {};
381       options.collection = this;
382       var model = new this.model(attrs, options);
383       if (!model.validationError) return model;
384       this.trigger('invalid', this, model.validationError, options);
385       return false;
386     },
387 
388     // Internal method called by both remove and set.
389     _removeModels: function(models, options) {
390       var removed = [];
391       for (var i = 0; i < models.length; i++) {
392         var model = this.get(models[i]);
393         if (!model) continue;
394 
395         var index = this.indexOf(model);
396         this.models.splice(index, 1);
397         this.length--;
398 
399         // Remove references before triggering 'remove' event to prevent an
400         // infinite loop. #3693
401         delete this._byId[model.cid];
402         var id = this.modelId(model.attributes);
403         if (id != null) delete this._byId[id];
404 
405         if (!options.silent) {
406           options.index = index;
407           model.trigger('remove', model, this, options);
408         }
409 
410         removed.push(model);
411         this._removeReference(model, options);
412       }
413       return removed;
414     },
415 
416     // Method for checking whether an object should be considered a model for
417     // the purposes of adding to the collection.
418     _isModel: function(model) {
419       return model instanceof Model;
420     },
421 
422     // Internal method to create a model's ties to a collection.
423     _addReference: function(model, options) {
424       this._byId[model.cid] = model;
425       var id = this.modelId(model.attributes);
426       if (id != null) this._byId[id] = model;
427       model.on('all', this._onModelEvent, this);
428     },
429 
430     // Internal method to sever a model's ties to a collection.
431     _removeReference: function(model, options) {
432       delete this._byId[model.cid];
433       var id = this.modelId(model.attributes);
434       if (id != null) delete this._byId[id];
435       if (this === model.collection) delete model.collection;
436       model.off('all', this._onModelEvent, this);
437     },
438 
439     // Internal method called every time a model in the set fires an event.
440     // Sets need to update their indexes when models change ids. All other
441     // events simply proxy through. "add" and "remove" events that originate
442     // in other collections are ignored.
443     _onModelEvent: function(event, model, collection, options) {
444       if (model) {
445         if ((event === 'add' || event === 'remove') && collection !== this) return;
446         if (event === 'destroy') this.remove(model, options);
447         if (event === 'change') {
448           var prevId = this.modelId(model.previousAttributes());
449           var id = this.modelId(model.attributes);
450           if (prevId !== id) {
451             if (prevId != null) delete this._byId[prevId];
452             if (id != null) this._byId[id] = model;
453           }
454         }
455       }
456       this.trigger.apply(this, arguments);
457     }
458 
459   });
460 
461   // Underscore methods that we want to implement on the Collection.
462   // 90% of the core usefulness of Backbone Collections is actually implemented
463   // right here:
464   var collectionMethods = {forEach: 3, each: 3, map: 3, collect: 3, reduce: 0,
465       foldl: 0, inject: 0, reduceRight: 0, foldr: 0, find: 3, detect: 3, filter: 3,
466       select: 3, reject: 3, every: 3, all: 3, some: 3, any: 3, include: 3, includes: 3,
467       contains: 3, invoke: 0, max: 3, min: 3, toArray: 1, size: 1, first: 3,
468       head: 3, take: 3, initial: 3, rest: 3, tail: 3, drop: 3, last: 3,
469       without: 0, difference: 0, indexOf: 3, shuffle: 1, lastIndexOf: 3,
470       isEmpty: 1, chain: 1, sample: 3, partition: 3, groupBy: 3, countBy: 3,
471       sortBy: 3, indexBy: 3, findIndex: 3, findLastIndex: 3};
472 
473   // Mix in each Underscore method as a proxy to `Collection#models`.
474   addUnderscoreMethods(Collection, collectionMethods, 'models');
原文地址:https://www.cnblogs.com/wangwei1314/p/5595864.html