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