Backbone.js源码分析系列之Collection模块

来源:互联网 发布:侧卧按键开关封装淘宝 编辑:程序博客网 时间:2024/06/05 00:10

Collection模块简介

Collection模块是对Model模块的创建、存储与删除等操作的一个集合。其中提供了对Model进行方便管理的各种方法(包括从Underscore.js扩展来的各种操作方法),实现了对一个应用中有多个数据模型的情形下的便利操作。

Collection模块源码注释解析

// Backbone.Collection  // -------------------  // If models tend to represent a single row of data, a Backbone Collection is  // more analogous to a table full of data ... or a small slice or page of that  // table, or a collection of rows that belong together for a particular reason  // -- all of the messages in this particular folder, all of the documents  // belonging to this particular author, and so on. Collections maintain  // indexes of their models, both in order, and for lookup by `id`.  // 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.  // 如果模型代表着一个单一的数据行,那么Backbone的Collection就是一个由行数据组成的表格,或者说是这个表格的一部分或一页,或者是具有一个特定特性的行数据的集合。  // 集合通过"id"来查找model,并对model进行有序的维护。可以通过包含一些特定类型的model来创建一个新的集合。  // 如果指定了`comparator`,那么集合里的model就是有序的,即使某个model被添加和被删除,整个集合的model也会保持有序性。  var Collection = Backbone.Collection = function(models, options) {// 开始创建集合类    options || (options = {});// 保证该上下文环境中的options是一个可用的对象// 根url,通过该url并将model的id附加在链接末尾来跟服务器同步数据(可见Model的url方法)    if (options.url) this.url = options.url;// 在配置参数中设置集合的模型类,默认是Backbone.Model    if (options.model) this.model = options.model;// comparator作为排序的准则,如果在选项参数中指定,则将其赋给Collection的实例    if (options.comparator !== void 0) this.comparator = options.comparator;// 初始化集合,指定几个内部私有工具属性    this._reset();// 执行初始化方法    this.initialize.apply(this, arguments);// 若指定了初始化时要添加的models,则添加进去    if (models) this.reset(models, _.extend({silent: true}, options));  };  // Default options for `Collection#set`.  // 定义Collection的默认操作,方便后面扩展  var setOptions = {add: true, remove: true, merge: true};  var addOptions = {add: true, merge: false, remove: false};  // 定义Collection可继承的方法  _.extend(Collection.prototype, Events, {// 对于Collection来说,其默认的model实现类是Backbone.Model。大多数情况下该属性都应该被重写    model: Model,// 在new一个Collection实例的时候都会自动执行该方法。它默认是一个空的方法,你可以用自己的初始化逻辑来覆盖它。    initialize: function(){},    // 返回一个数组, 包含了集合中每个模型的数据对象    toJSON: function(options) {// 通过Undersocre的map方法将集合中每一个模型的toJSON结果组成一个数组, 并返回return this.map(function(model){ // 依次调用每个模型对象的toJSON方法, 该方法默认将返回模型的数据对象(复制的副本)            // 如果需要返回字符串等其它形式, 可以重载toJSON方法return model.toJSON(options); });    },// 默认使用Backbone.sync来实现集合的sync    sync: function() {      return Backbone.sync.apply(this, arguments);    },    // 往集合中添加一个或者几个model实例,调用set方法来实现    add: function(models, options) {  // _.defaults:将第一个参数对象中属性值为undefined的属性用第二个参数对象的属性填充起来      return this.set(models, _.defaults(options || {}, addOptions));    },    // Remove a model, or a list of models from the set.// 从Collection集合中移除一个或一系列的model实例    remove: function(models, options) {  // 将models转化为数组,如果models已经是数组则返回它的副本,以防破坏原来的数组      models = _.isArray(models) ? models.slice() : [models];  // 处理参数常见做法,给options默认值      options || (options = {});      var i, l, index, model;  // 遍历每一个model实例(据说从后往前遍历一个数组速度会更快)      for (i = 0, l = models.length; i < l; i++) {// 以model的id获取到改model实例        model = this.get(models[i]);// 如果没有找到该model则跳出本次循环        if (!model) continue;// 找到了就将对应该id/cid的model从集合中删除        delete this._byId[model.id];        delete this._byId[model.cid];// 获取到改model在集合中的位置        index = this.indexOf(model);// 将其从models数组中删除        this.models.splice(index, 1);// 实时修改长度记录值        this.length--;// 如果没有设置silent参数则触发"remove"事件        if (!options.silent) {          options.index = index;  // 触发"remove"事件          model.trigger('remove', model, this, options);        }// 内部方法,用于将model实例对集合的引用删除掉,并去除掉有关集合的监听事件        this._removeReference(model);      }  // 返回this用于链式调用      return this;    },    // 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.// 更新集合的方法。可以增添一个模型列表,一个模型,删除不再使用的模型,合并已经存在于集合中的模型。// 默认会触发"all"事件, 如果在options中设置了silent属性, 可以关闭此次事件触发    // 传入的models可以是一个或一系列的模型对象(Model类的实例), 如果在集合中设置了model属性, 则允许直接传入数据对象(如 {name: 'test'}), 将自动将数据对象实例化为model指向的模型对象    set: function(models, options) {  // 使用setOptions来填充options中不存在的值      options = _.defaults(options || {}, setOptions);  // 如果options指定了parse方法(该方法一般自己扩展),则调用集合的parse方法      if (options.parse) models = this.parse(models, options);  // 将models设置为一个数组      if (!_.isArray(models)) models = models ? [models] : [];      var i, l, model, attrs, existing, sort;  // 设置新模型列表插入到集合中的位置, 如果在options中设置了at参数, 则在集合的at位置插入      // 默认将插入到集合的末尾      var at = options.at;  // 如果设置了comparator自定义排序方法, 则设置at后还将按照comparator中的方法进行排序, 因此最终的顺序可能并非在at指定的位置  // 指定是否要排序      var sortable = this.comparator && (at == null) && options.sort !== false;  // sortAttr设置为排序指标,指定其必须为字符串      var sortAttr = _.isString(this.comparator) ? this.comparator : null;      var toAdd = [], toRemove = [], modelMap = {};      // Turn bare objects into model references, and prevent invalid models from being added.  // 将空对象转换为模型引用,阻止非法模型对象被添加到集合中      for (i = 0, l = models.length; i < l; i++) {// 将数据对象转化为模型,如果数据不合法将直接跳过此模型不做设置        if (!(model = this._prepareModel(models[i], options))) continue;        // If a duplicate is found, prevent it from being added and        // optionally merge it into the existing model.// 如果模型的副本已经存在,将不再对模型对象做单独添加,而是将该模型与已存在的模型做合并        if (existing = this.get(model)) {  // 如果参数中设置了remove参数,则将已经存在的model的id记录在modelMap中,后续做删除时不会将此模型删除          if (options.remove) modelMap[existing.cid] = true;            if (options.merge) {// 做merge操作            existing.set(model.attributes, options);// 重新做排序的条件,如果原有model没有做任何改变也不做重新排序,减少不必要排序来提升性能            if (sortable && !sort && existing.hasChanged(sortAttr)) sort = true;          }  // This is a new model, push it to the `toAdd` list.  // 全新的model,将它放入toAdd列表中,后面做统一添加        } else if (options.add) {          toAdd.push(model);          // Listen to added models' events, and index models for lookup by          // `id` and by `cid`.  // 添加新的model会触发all事件          model.on('all', this._onModelEvent, this);  // 可以按model的cid找到该模型          this._byId[model.cid] = model;  // 如果自己指定了模型id则使用自己指定的id值做索引          if (model.id != null) this._byId[model.id] = model;        }      }      // Remove nonexistent models if appropriate.  // 模型删除操作      if (options.remove) {        for (i = 0, l = this.length; i < l; ++i) {  // 模型删除,但是指定的模型若已经存在则不做删除(留下指定的模型,删除其他的)          if (!modelMap[(model = this.models[i]).cid]) toRemove.push(model);        }// 删除操作        if (toRemove.length) this.remove(toRemove, options);      }      // See if sorting is needed, update `length` and splice in new models.  // 模型添加操作      if (toAdd.length) {// 如果指定了排序,则可以既进行排序(对已经存在的模型进行了更新也会进行排序)        if (sortable) sort = true;// 记录集合中的模型数量        this.length += toAdd.length;// 指定了模型存放的位置,则将所有的模型从指定位置开始添加        if (at != null) {          splice.apply(this.models, [at, 0].concat(toAdd));        } else {  // 没有制定添加的位置则添加在列表末尾          push.apply(this.models, toAdd);        }      }      // Silently sort the collection if appropriate.  // 静态的进行排序      if (sort) this.sort({silent: true});  // 如果设置了不触发监听事件,则返回,否则触发add和sort事件      if (options.silent) return this;      // Trigger `add` events.  // 触发所有的add事件      for (i = 0, l = toAdd.length; i < l; i++) {        (model = toAdd[i]).trigger('add', model, this, options);      }      // Trigger `sort` if the collection was sorted.      if (sort) this.trigger('sort', this, options);  // 链式调用      return this;    },    // 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.// 替换集合中的所有模型数据(models)    // 该操作将删除集合中当前的所有数据和状态, 并重新将数据设置为models    // models应该是一个数组, 可以包含一系列Model模型对象, 或原始对象(将在add方法中自动创建为模型对象)    reset: function(models, options) {      // models是进行替换的模型(或数据)数组      options || (options = {});  // 遍历当前集合中的模型, 依次删除并解除它们与集合的引用关系      for (var i = 0, l = this.models.length; i < l; i++) {        this._removeReference(this.models[i]);      }  // 将以前的models做缓存      options.previousModels = this.models;  // 删除集合数据并重置状态      this._reset();  // 通过add方法将新的模型数据添加到集合      // 这里通过exnted方法将配置项覆盖到一个新的对象, 该对象默认silent为true, 因此不会触发"add"事件      // 如果在调用reset方法时没有设置silent属性则会触发reset事件, 如果设置为true则不会触发任何事件, 如果设置为false, 将依次触发"add"和"reset"事件      this.add(models, _.extend({silent: true}, options));  // 如果在调用reset方法时没有设置silent属性, 则触发reset事件      if (!options.silent) this.trigger('reset', this, options);      return this;    },// 模仿数组方法,讲一个model实例存放于集合的尾处    push: function(model, options) {  // 准备model实例(model可以是Model的实例,也可以是要set给model的数据)      model = this._prepareModel(model, options);  // 将model实例添加到集合      this.add(model, _.extend({at: this.length}, options));      return model;    },    // 从集合的尾部删除一个model实例    pop: function(options) {  // 通过索引获取到对应的model      var model = this.at(this.length - 1);  // 将model从集合中移除      this.remove(model, options);      return model;    },    // 将model从集合的头部加入    unshift: function(model, options) {      model = this._prepareModel(model, options);   // 调用add方法将模型插入到集合的第一个位置(设置at为0)       // 如果定义了comparator排序方法, 集合的顺序将被重排      this.add(model, _.extend({at: 0}, options));  // 返回模型对象      return model;    },    // 移除并返回集合中的第一个模型对象    shift: function(options) {  // 获得集合中的第一个模型      var model = this.at(0);  // 从集合中删除该模型      this.remove(model, options);  // 返回模型对象      return model;    },    // 从集合的models中取出一个子数组    slice: function(begin, end) {      return this.models.slice(begin, end);    },// 从集合中通过id来获取model    get: function(obj) {  // 如果没有传入obj则返回undefined      if (obj == null) return void 0;  // this._byId为集合中以id为属性存model的对象集合。obj参数可以是带有id的对象也可以直接是id      return this._byId[obj.id != null ? obj.id : obj.cid || obj];    },// 通过索引返回对应的model实例    at: function(index) {      return this.models[index];    },    // Return models with matching attributes. Useful for simple cases of `filter`.// 返回匹配attrs中属性的model集合,对于过滤model数据很有用    where: function(attrs, first) {  // 如果attrs是一个空对象,如果传入了first参数则返回undefined,否则返回空数组      if (_.isEmpty(attrs)) return first ? void 0 : [];  // 通过集合实例的find(如果要找出第一个符合条件的)或filter(找到所有符合条件的)来找到最终的结果      return this[first ? 'find' : 'filter'](function(model) {        for (var key in attrs) {  // 将attrs中的验证规则与集合中的模型进行匹配          if (attrs[key] !== model.get(key)) return false;        }        return true;      });    },    // 找到第一个匹配attrs条件的model。    findWhere: function(attrs) {      return this.where(attrs, true);    },    // 对集合中的模型按照comparator属性指定的方法进行排序    // 如果没有在options中设置silent参数, 则排序后将触发reset事件    sort: function(options) {  // 排序的指标必须已经存在      if (!this.comparator) throw new Error('Cannot sort a set without a comparator');  // options默认是一个对象      options || (options = {});      // comparator属性是一个字符串或者拥有值为1的length属性      if (_.isString(this.comparator) || this.comparator.length === 1) {// 默认使用underscore的sortBy方法来进行排序        this.models = this.sortBy(this.comparator, this);      } else {// comparator可能是一个函数的情形,调用原生的sort方法        this.models.sort(_.bind(this.comparator, this));      }  // 如果没有制定silent属性,则触发sort事件      if (!options.silent) this.trigger('sort', this, options);  // 返回this以实现链式调用      return this;    },    // Figure out the smallest index at which a model should be inserted so as    // to maintain order.// 获取一个model应该再集合中插入的位置,以保持顺序性    sortedIndex: function(model, value, context) {  // 没传入value则将value默认为集合的comprater值      value || (value = this.comparator);  // 排序的迭代器      var iterator = _.isFunction(value) ? value : function(model) {        return model.get(value);      };  // 调用underscore的排序方法来进行排序      return _.sortedIndex(this.models, model, iterator, context);    },    // 将集合中所有模型的attr属性值存放到一个数组并返回    pluck: function(attr) {  // underscore中的invoke方法,对this.models中的每一个子项都执行get方法,attr作为参数传入(要保证所有的子项都具有该方法,否则会报错)      return _.invoke(this.models, 'get', attr);    },    // Fetch the default set of models for this collection, resetting the    // collection when they arrive. If `reset: true` is passed, the response    // data will be passed through the `reset` method instead of `set`.// 从服务器获取集合的初始化数据    // 如果在options中设置参数reset: true, 则获取到的数据会替换集合中的当前数据, 否则将以服务器返回的数据被追加到集合中    fetch: function(options) {  // 复制options对象, 因为options对象在后面会被修改用于临时存储数据      options = options ? _.clone(options) : {};      if (options.parse === void 0) options.parse = true;  // 自定义回调函数, 数据请求成功后并添加完成后, 会调用自定义success函数      var success = options.success;  // collection记录当前集合对象, 用于在success回调函数中使用      var collection = this;  // 当从服务器请求数据成功时执行options.success, 该函数中将解析并添加数据      options.success = function(resp) {// 通过parse方法对服务器返回的数据进行解析, 如果需要自定义数据结构, 可以重载parse方法        // 如果在options中设置reset=true, 则调用reset方法将数据重置到集合, 否则将服务器的返回数据通过set方法添加到集合中        var method = options.reset ? 'reset' : 'set';        collection[method](resp, options);// 如果设置了自定义成功回调, 则执行        if (success) success(collection, resp, options);// 触发sync方法        collection.trigger('sync', collection, resp, options);      };  // 当服务器返回状态错误时, 通过wrapError方法处理错误事件      wrapError(this, options);  // 调用集合的sync方法(其实是Backbone的)发送请求从服务器获取数据  // 如果需要的数据并不是从服务器获取, 或获取方式不使用AJAX, 可以重载Backbone.sync方法      return this.sync('read', this, options);    },    // Create a new instance of a model in this collection. Add the model to the    // collection immediately, unless `wait: true` is passed, in which case we    // wait for the server to agree.// 在集合中创建一个模型的新的实例// 如果在options中声明了wait属性, 则会在服务器创建成功后再将模型添加到集合, 否则先将模型添加到集合, 再保存到服务器(无论保存是否成功)    create: function(model, options) {  // 同样对options实现一份拷贝,以免修改原来对象      options = options ? _.clone(options) : {};  // 通过_prepareModel获取模型类的实例,创建模型失败则直接返回      if (!(model = this._prepareModel(model, options))) return false;  // 如果没有声明wait属性, 则通过add方法将模型添加到集合中      if (!options.wait) this.add(model, options);      var collection = this;  // success存储保存到服务器成功之后的自定义回调函数(通过options.success声明)      var success = options.success;      options.success = function(resp) {    // 如果声明了wait属性, 则在只有在服务器保存成功后才会将模型添加到集合中        if (options.wait) collection.add(model, options);// 如果声明了自定义成功回调, 则执行自定义函数        if (success) success(model, resp, options);      };  // 调用模型的save方法, 将模型数据保存到服务器      model.save(null, options);  // 将创建的model返回      return model;    },    // 数据解析方法, 用于将服务器数据解析为模型和集合可用的结构化数据    // 默认将返回resp本身, 这需要与服务器定义Backbone支持的数据格式, 如果需要自定义数据格式, 可以重载parse方法    parse: function(resp, options) {      return resp;    },    // 返回集合类的一个实例    clone: function() {      return new this.constructor(this.models);    },// 内部方法来重置内部所有的状态。当集合首次被初始化或者被重置时会执行    _reset: function() {      this.length = 0;      this.models = [];      this._byId  = {};    },    // Prepare a hash of attributes (or other model) to be added to this collection.// 将模型添加到集合中之前的一些准备工作    // 包括将数据实例化为一个模型对象, 和将集合引用到模型的collection属性    _prepareModel: function(attrs, options) {  // 判断如果attrs是Model的实例      if (attrs instanceof Model) {// 为该model实例添加对当前collection实例的引用        if (!attrs.collection) attrs.collection = this;        return attrs;      }  // 为options设置默认值      options || (options = {});  // 为options添加对当前集合实例的饮用      options.collection = this;  // 创建model实例      var model = new this.model(attrs, options);  // 添加model数据值检测,如果检测没有通过则触发invalidate事件,并返回false      if (!model._validate(attrs, options)) {        this.trigger('invalid', this, attrs, options);        return false;      }      return model;    },// 将model中用于对Collection的引用删除掉,并去除所有有关集合的事件    _removeReference: function(model) {      if (this === model.collection) delete model.collection;      model.off('all', this._onModelEvent, this);    },    // Internal method called every time a model in the set fires an event.    // Sets need to update their indexes when models change ids. All other    // events simply proxy through. "add" and "remove" events that originate    // in other collections are ignored.// 内部方法,用于在每次往集合中set模型的时候触发事件统一管理。// 用于监听集合中模型的事件, 当模型在触发事件(add, remove, destroy, change事件)时集合进行相关处理    _onModelEvent: function(event, model, collection, options) {  // 其他集合的add和remove事件被忽略      if ((event === 'add' || event === 'remove') && collection !== this) return;  // 模型触发销毁事件时, 从集合中移除      if (event === 'destroy') this.remove(model, options);  // 当模型的id被修改时, 集合修改_byId中存储对模型的引用, 保持与模型id的同步, 便于使用get()方法获取模型对象      if (model && event === 'change:' + model.idAttribute) {// 获取模型在改变之前的id, 并根据此id从集合的_byId列表中移除        delete this._byId[model.previous(model.idAttribute)];// 以模型新的id作为key, 在_byId列表中存放对模型的引用        if (model.id != null) this._byId[model.id] = model;      }   // 在集合中触发模型对应的事件, 无论模型触发任何事件, 集合都会触发对应的事件       // (例如当模型被添加到集合中时, 会触发模型的"add"事件, 同时也会在此方法中触发集合的"add"事件)       // 这对于监听并处理集合中模型状态的变化非常有效      // 在监听的集合事件中, 触发对应事件的模型会被作为参数传递给集合的监听函数      this.trigger.apply(this, arguments);    }  });  // Underscore methods that we want to implement on the Collection.  // 90% of the core usefulness of Backbone Collections is actually implemented  // right here:  // 定义Underscore中的集合操作的相关方法    // 将Underscore中一系列集合操作方法复制到Collection集合类的原型对象中    // 这样就可以直接通过集合对象调用Underscore相关的集合方法    // 这些方法在调用时所操作的集合数据是当前Collection对象的models数据  var methods = ['forEach', 'each', 'map', 'collect', 'reduce', 'foldl',    'inject', 'reduceRight', 'foldr', 'find', 'detect', 'filter', 'select',    'reject', 'every', 'all', 'some', 'any', 'include', 'contains', 'invoke',    'max', 'min', 'toArray', 'size', 'first', 'head', 'take', 'initial', 'rest',    'tail', 'drop', 'last', 'without', 'indexOf', 'shuffle', 'lastIndexOf',    'isEmpty', 'chain'];  // Mix in each Underscore method as a proxy to `Collection#models`.  // 遍历已经定义的方法列表  _.each(methods, function(method) {  // 将方法复制到Collection集合类的原型对象    Collection.prototype[method] = function() {// 调用时直接使用Underscore的方法, 上下文对象保持为Underscore对象        // 需要注意的是这里传递给Underscore方法的集合参数是 this.models, 因此在使用这些方法时, 所操作的集合对象是当前Collection对象的models数据      var args = slice.call(arguments);      args.unshift(this.models);      return _[method].apply(_, args);    };  });  // Underscore methods that take a property name as an argument.  // Underscore中以迭代器作为参数的方法  var attributeMethods = ['groupBy', 'countBy', 'sortBy'];  // Use attributes instead of properties.  _.each(attributeMethods, function(method) {    Collection.prototype[method] = function(value, context) {  // 获取当前的迭代函数      var iterator = _.isFunction(value) ? value : function(model) {        return model.get(value);      };  // 调用Underscore对应的方法来执行      return _[method](this.models, iterator, context);    };  });


0 0