光域

<html/>

Backbone源码学习 — Backbone.Collection

今天我们继续来说说Backbone MVC中的 M,如果说前面介绍的Backbone.Model像是数据库中的一条记录的抽象的话,那么今天要聊的Backbone.Collection 就像是由若干条Backbone.Model组成的一个数据库表,其api和使用方法和Backbone.Model相似,主要是对models集合进行操作,以下为Backbone.Collection提供的API:

我们可以看到很多api和Backbone.Model 的方法是相同的,主要是用于操作数据,进行数据定位等等,方法有:

  • set:作为所有数据操作的基类方法,通过options确定具体的操作,主要是对models 集合进行add,remove,merge等操作,默认操作是将传入的models替换原有的models,不进行合并
  • add:封装了set方法,传入的配置默认是remove:false,将存入的models添加到原有的models中去
  • remove:删除传入的models,如果传入的model不存在,则为空操作,删除之后触发 remove 事件
  • reset : 删除原来的models集合 ,将传入的models进行设置,不触发add/remove事件,在完成之后触发reset事件
  • get : 通过传入的参数返回特定的model(参数可以是obj/id/cid);

下面我们来看看几个主要的API:

set

collection.set(models, [options])

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
   //set/add操作中传入set 的默认option
  var setOptions = {add: true, remove: true, merge: true};
  var addOptions = {add: true, remove: false};

  set:function(models, options){
         //各种属性设置
            options = _.defaults({}, options, setOptions);

        //parse 解析models
        if (options.parse) models = this.parse(models, options);

        //判断是否是当个model,如果是则返回一个[model]
        var singular = !_.isArray(models);
        models = singular ? (models ? [models] : []) : _.clone(models);
      
        // 各种options 的判断
        var i, l, id, model, attrs, existing, sort;
        // 如果传入了at属性,则会在插入的时候插入到at指定的models的位置
        var at = options.at;
        var targetModel = this.model;

        //如果comparator不为空,没有指定at,sort不为false,则这次set操作将进行排序
        var sortable = this.comparator && (at == null) && options.sort !== false;
        var sortAttr = _.isString(this.comparator) ? this.comparator : null;
          //设置各个操作的临时存储集合
        var toAdd = [], toRemove = [], modelMap = {};
        var add = options.add, merge = options.merge, remove = options.remove;
        var order = !sortable && add && remove ? [] : false;
      


    //开始对传入的models进行处理,但是只是对models进行分类,传入到各个临时存储集合中去
      for (i = 0, l = models.length; i < l; i++) {
        attrs = models[i] || {};

      //获取model的id
        if (attrs instanceof Model) { //是否为 Backbone.Model (没有设置model)
          id = model = attrs;
        } else {
          id = attrs[targetModel.prototype.idAttribute || 'id'];
        }


        //判断是否已经存在
        if (existing = this.get(id)) {
          if (remove) modelMap[existing.cid] = true; //如果 remove 为true,加入modelMap
          if (merge) { //merge 为true, 对model中的attribute 进行设置
            attrs = attrs === model ? model.attributes : attrs;
            if (options.parse) attrs = existing.parse(attrs, options);
            existing.set(attrs, options);//调用model中的set方法,进行数据合并
            if (sortable && !sort && existing.hasChanged(sortAttr)) sort = true;
          }
          models[i] = existing;


        } else if (add) {//若不存在则判断add属性,存入到toAdd 中

        //调用_prepareModel生成一个model
          model = models[i] = this._prepareModel(attrs, options);
          if (!model) continue;
          toAdd.push(model); //添加入toAdd
          this._addReference(model, options) //建立与collection的关联
        }

        model = existing || model;
        if (order && (model.isNew() || !modelMap[model.id])) order.push(model);
        modelMap[model.id] = true;
      }
      
    //添加model 处理 
      if (toAdd.length || (order && order.length)) {
        if (sortable) sort = true;
        this.length += toAdd.length;//更新length的值
        if (at != null) {//如果at值不为空,则插入到对应的索引位置
          for (i = 0, l = toAdd.length; i < l; i++) {
            this.models.splice(at + i, 0, toAdd[i]);
          }
        } else {//否则使用push向后插入,如果order不为空,则使用order替换整个models
          if (order) this.models.length = 0;
          var orderedModels = order || toAdd;
          for (i = 0, l = orderedModels.length; i < l; i++) {
            this.models.push(orderedModels[i]);
          }
        }
      }

      if (sort) this.sort({silent: true}); // 排序

      //触发 响应的事件
      if (!options.silent) {
        for (i = 0, l = toAdd.length; i < l; i++) {
          (model = toAdd[i]).trigger('add', model, this, options);
        }
        if (sort || (order && order.length)) this.trigger('sort', this, options);
      }

      return singular ? models[0] : models;         
  
  }

set方法中通过对options的remove,add,merge进行设置,进行不同的行为操作,如果remove是true的话,其值将被设置入order中,在line:79 进行整个models的替换,如果是remove是false,则将set 的值加入toAdd中。Backbone.Collection中很多方法都是以set为基础方法的,如 add方法:

1
2
3
    add: function(models, options) {
        return this.set(models, _.extend({merge: false}, options, addOptions));
   }

_addReference

_addReference 方法为内部方法,主要用于在model和对应的collection建立关系,在collection的_byId集合中加入对应cidid的model映射,将model的collection属性设置为当前的collection 。

1
2
3
4
5
6
   _addReference: function(model, options) {
      this._byId[model.cid] = model;
      if (model.id != null) this._byId[model.id] = model;
      if (!model.collection) model.collection = this;
      model.on('all', this._onModelEvent, this);
    }

除了set方法,其他的方法和Backbone.Model中的方法从逻辑,机制都差不多,都是一个 操作 --> 触发事件的过程。只有少数差别,比如fetch方法 ,在options中有个reset配置,可以对获取的数据进行一个覆盖操作。而Backbone.Collection中没有save方法,取而代之的是一个create方法,就是调用了model中的save方法,但是细细推敲,在collection中使用create这个名字确实比较准确