Backbone Collection 源碼簡談

一切由一個例子引起:數組

 1 var Man=Backbone.Model.extend({
 2          initilize:function(){
 3             this.bind('change:name',function(){
 4                console.log('更名了');
 5             });
 6          },
 7          validate:function(){
 8              if(this.attributes.name=='jack'){
 9                 console.log('不一樣意');
10              }
11          }
12      });
13      var man=new Man();
14      man.set({'name':'jack','sex':'man','age':'12'},{validate:false});
15      
16      var Mans=Backbone.Collection.extend({
17         model:man
18      });
19      var tom=new Man({name:'tom'});
20      var mary=new Man({name:'mary'});
21      var mans=new Mans();
22      mans.add(tom);
23      mans.add(mary);
24      mans.each(function(m){
25          console.log(m.get('name'));// tom  mary
26      });
View Code

Collection的實例化聲明很簡單,初級使用只用了model屬性,而後涉及到了 add方法和each遍歷方法。
Collection 工廠app

 1  // Create a new **Collection**, perhaps to contain a specific type of `model`.
 2   // If a `comparator` is specified, the Collection will maintain
 3   // its models in sort order, as they're added and removed.
 4   var Collection = Backbone.Collection = function(models, options) {
 5     options || (options = {});
 6     if (options.model) this.model = options.model;
 7     if (options.comparator !== void 0) this.comparator = options.comparator;
 8     this._reset();
 9     this.initialize.apply(this, arguments);
10     if (models) this.reset(models, _.extend({silent: true}, options));
11   };
View Code

 以上是collection的源碼less

 collection的參數:ide

models: array  由model-object組成的數組。函數

options: object 配置型信息,包括如下幾個屬性:this

             model: functionspa

             comparator: stringprototype

             silent: bool 是否禁止觸發事件3d

             sort: bool 是否進行排序rest

             at:number 用於肯定添加models時從何位置添加 

             add:

             merge:

             remove:

工廠函數執行步驟以下:

1.進行參數的調整和初始化分配

2.對collection-object進行重置,重置操做代碼以下:

1  // Private method to reset all internal state. Called when the collection
2     // is first initialized or reset.
3     _reset: function() {
4       this.length = 0;
5       this.models = [];
6       this._byId  = {};
7     },
View Code

3.執行collection-object的初始化操做,initialize函數默認爲空函數
4.關鍵的最後一步,使用reset方法對collection-object附加屬性

 1  // When you have more items than you want to add or remove individually,
 2     // you can reset the entire set with a new list of models, without firing
 3     // any granular `add` or `remove` events. Fires `reset` when finished.
 4     // Useful for bulk operations and optimizations.
 5     reset: function(models, options) {
 6       options || (options = {});
 7       for (var i = 0, l = this.models.length; i < l; i++) {
 8         this._removeReference(this.models[i]);
 9       }
10       options.previousModels = this.models;
11       this._reset();
12       models = this.add(models, _.extend({silent: true}, options));
13       if (!options.silent) this.trigger('reset', this, options);
14       return models;
15     },
View Code

工廠函數分解完畢。下面咱們就來看看關鍵的 reset方法都作了什麼?

reset(models,options)

reset函數的兩個參數均來自工廠函數

reset執行步驟以下:

 1.首先仍是參數的準備

 2.遍歷models並對models進行_removeReference處理

1 // Internal method to sever a model's ties to a collection.
2     _removeReference: function(model) {
3       if (this === model.collection) delete model.collection;
4       model.off('all', this._onModelEvent, this);
5     },
View Code

 經過查看源碼,咱們發現,處理後的model-object沒有了collection屬性,由於該屬性保存了它原來隸屬於的collection。 同時暫時關閉了model-object上的all方法。
 3.再次調用_reset()對collection對象進行重置

 4.調用add方法,其實add就是set方法,下面咱們講到add的時候就直接說set方法:

1 // Add a model, or list of models to the set.
2     add: function(models, options) {
3       return this.set(models, _.extend({merge: false}, options, addOptions));
4     },
View Code

5.執行 reset 事件,若有則執行。
6.返回參數1

reset分解結束。

上一單元咱們提到了reset中調用了add,而add的實質其實就是set方法,那麼接下來就八一八set

set(models, options)

其參數來自於工廠函數。不一樣的是,add對options中的merge字段作了強制處理。致使set接收的options變了味兒,其中就附加了

 var addOptions = {add: true, remove: false};

和{merge:false}

如下是set源碼:

 1 // Update a collection by `set`-ing a new list of models, adding new ones,
 2     // removing models that are no longer present, and merging models that
 3     // already exist in the collection, as necessary. Similar to **Model#set**,
 4     // the core operation for updating the data contained by the collection.
 5     set: function(models, options) {
 6       options = _.defaults({}, options, setOptions);
 7       if (options.parse) models = this.parse(models, options);
 8       var singular = !_.isArray(models);
 9       models = singular ? (models ? [models] : []) : _.clone(models);
10       var i, l, id, model, attrs, existing, sort;
11       var at = options.at;
12       var targetModel = this.model;
13       var sortable = this.comparator && (at == null) && options.sort !== false;
14       var sortAttr = _.isString(this.comparator) ? this.comparator : null;
15       var toAdd = [], toRemove = [], modelMap = {};
16       var add = options.add, merge = options.merge, remove = options.remove;
17       var order = !sortable && add && remove ? [] : false;
18 
19       // Turn bare objects into model references, and prevent invalid models
20       // from being added.
21       for (i = 0, l = models.length; i < l; i++) {
22         attrs = models[i];
23         if (attrs instanceof Model) {
24           id = model = attrs;
25         } else {
26           id = attrs[targetModel.prototype.idAttribute];
27         }
28 
29         // If a duplicate is found, prevent it from being added and
30         // optionally merge it into the existing model.
31         if (existing = this.get(id)) {
32           if (remove) modelMap[existing.cid] = true;
33           if (merge) {
34             attrs = attrs === model ? model.attributes : attrs;
35             if (options.parse) attrs = existing.parse(attrs, options);
36             existing.set(attrs, options);
37             if (sortable && !sort && existing.hasChanged(sortAttr)) sort = true;
38           }
39           models[i] = existing;
40 
41         // If this is a new, valid model, push it to the `toAdd` list.
42         } else if (add) {
43           model = models[i] = this._prepareModel(attrs, options);
44           if (!model) continue;
45           toAdd.push(model);
46 
47           // Listen to added models' events, and index models for lookup by
48           // `id` and by `cid`.
49           model.on('all', this._onModelEvent, this);
50           this._byId[model.cid] = model;
51           if (model.id != null) this._byId[model.id] = model;
52         }
53         if (order) order.push(existing || model);
54       }
55 
56       // Remove nonexistent models if appropriate.
57       if (remove) {
58         for (i = 0, l = this.length; i < l; ++i) {
59           if (!modelMap[(model = this.models[i]).cid]) toRemove.push(model);
60         }
61         if (toRemove.length) this.remove(toRemove, options);
62       }
63 
64       // See if sorting is needed, update `length` and splice in new models.
65       if (toAdd.length || (order && order.length)) {
66         if (sortable) sort = true;
67         this.length += toAdd.length;
68         if (at != null) {
69           for (i = 0, l = toAdd.length; i < l; i++) {
70             this.models.splice(at + i, 0, toAdd[i]);
71           }
72         } else {
73           if (order) this.models.length = 0;
74           var orderedModels = order || toAdd;
75           for (i = 0, l = orderedModels.length; i < l; i++) {
76             this.models.push(orderedModels[i]);
77           }
78         }
79       }
80 
81       // Silently sort the collection if appropriate.
82       if (sort) this.sort({silent: true});
83 
84       // Unless silenced, it's time to fire all appropriate add/sort events.
85       if (!options.silent) {
86         for (i = 0, l = toAdd.length; i < l; i++) {
87           (model = toAdd[i]).trigger('add', model, this, options);
88         }
89         if (sort || (order && order.length)) this.trigger('sort', this, options);
90       }
91       
92       // Return the added (or merged) model (or models).
93       return singular ? models[0] : models;
94     },
View Code

1.老一套,參數準備,把models強制變成數組
2.局部變量的準備,爲運行打好基礎。

3.遍歷models這個數組,根據不一樣狀況取id,第一種id其實就是model-object.

4.在遍歷的過程當中判斷這個model-object是否已經存在於collection-object中(即檢測_byId對象,get也是用於檢測_byId),若是存在:

  options.remove是否爲true,若是是則整理modelMap

  options.merge 若是爲true, 將已有的model-object和當前的model-object合併

  將處理完的 existing model-object放入models數組的當前位置中

5.若是不存在且options.add爲true:

  將當前model存入toAdd數組

  開啓該model-object的all事件(在工廠函數的時候關過一次)

  將該model-object存入_byId對象

6.若是order存在且是數組,則將existing||model push進去 循環到此結束

7.若是options.remove存在

  遍歷循環this,也就是collection-class={length:model-object個數,_byId:{cid:model-object},models:[model-object]}

  還記得上面那個循環維護的modelMap嗎?這時就要派上用場,modelMap裏面記錄的都是已經存在的model-object.cid:true,根據上述判斷,對於不知足條件的model-object將放入toRemove數組

  遍歷完畢後檢查toRemove數組,而後執行remove操做,remove操做詳細看分割線下之下第一個函數

8.而後檢測是否存在toAdd或者order數組:

   若是不須要排序且指定了at 位置,則按照位置插入model-object

   不然將this.models置空,將order或toAdd存入其中

 9.若是存在排序,則調用this.sort,sort方法看分割線下第二個函數

 10.進行事件處理,分別根據狀況選擇觸發add 和 sort函數

 11.根據傳入參數是否單一決定返回model-object 或 models

至此set函數分析完畢

結論:創建一個collection-object,依次會調用: 工廠函數->reset函數->set函數

工廠函數的一條線分析完畢,綜上分析collection的核心其實就是models數組和_byId對象

collection的相關操做

at(index):獲取該索引下的model

 

1 // Get the model at the given index.
2     at: function(index) {
3       return this.models[index];
4     },
View Code

其實就是取出collection-object.models[index]

 

-----------------------------------------------collection 內部函數----------------------------------

 1.remove(models, options)

 參數:   models:model-object 的集合

              options: object 配置對象

              兩者均來自collection 工廠函數

 做用: 從collection中刪除單個model-object 或 多個model-object

 原理: 1.仍是進行參數的保障性處理和局部變量的準備

            2.遍歷這個即將要被刪除掉的數組

            3.在遍歷過程當中針對_byId進行刪除工做

            4.在遍歷過程當中針對collection-object.models屬性進行刪除操做

            5.對collection-object.length屬性進行操做

            6.在遍歷過程當中options.silent沒有聲明,則觸發remove事件,若是有all也會觸發

            7.關閉當前models[i]的all事件,遍歷結束

            8.返回通過處理後的第一個參數

 源碼:

 1 // Remove a model, or a list of models from the set.
 2     remove: function(models, options) {
 3       var singular = !_.isArray(models);
 4       models = singular ? [models] : _.clone(models);
 5       options || (options = {});
 6       var i, l, index, model;
 7       for (i = 0, l = models.length; i < l; i++) {
 8         model = models[i] = this.get(models[i]);
 9         if (!model) continue;
10         delete this._byId[model.id];
11         delete this._byId[model.cid];
12         index = this.indexOf(model);
13         this.models.splice(index, 1);
14         this.length--;
15         if (!options.silent) {
16           options.index = index;
17           model.trigger('remove', model, this, options);
18         }
19         this._removeReference(model);
20       }
21       return singular ? models[0] : models;
22     },
View Code


2.sort(options)

   參數:options object  配置對象

   做用:用於collection中model-object的排序,通常咱們不會用到它,使用sort的前提是collection-object中定義comparator屬性

   原理:1.首先檢測是否包含comparator屬性,沒有則拋出 Cannot sort a set without a comparator  錯誤

              2.而後檢測 comparator是否爲字符串或長度是否爲1

                經過檢測則調用sortBy函數進行排序

                不然使用underscore.bind方法排序

             3.根據options.silent決定是否觸發sort事件

             4.返回this   collection-object

   源碼:

 1 // Force the collection to re-sort itself. You don't need to call this under
 2     // normal circumstances, as the set will maintain sort order as each item
 3     // is added.
 4     sort: function(options) {
 5       if (!this.comparator) throw new Error('Cannot sort a set without a comparator');
 6       options || (options = {});
 7 
 8       // Run sort based on type of `comparator`.
 9       if (_.isString(this.comparator) || this.comparator.length === 1) {
10         this.models = this.sortBy(this.comparator, this);
11       } else {
12         this.models.sort(_.bind(this.comparator, this));
13       }
14 
15       if (!options.silent) this.trigger('sort', this, options);
16       return this;
17     },
View Code

  看到這裏你們有些奇怪。這個sortBy和_.bind到底是啥東西呢?我們接着往下扒:
  sortBy集成於這種機制: 它的兄弟還有 countBy  和 groupBy

 1   // Underscore methods that take a property name as an argument.
 2   var attributeMethods = ['groupBy', 'countBy', 'sortBy'];
 3 
 4   // Use attributes instead of properties.
 5   _.each(attributeMethods, function(method) {
 6     Collection.prototype[method] = function(value, context) {
 7       var iterator = _.isFunction(value) ? value : function(model) {
 8         return model.get(value);
 9       };
10       return _[method](this.models, iterator, context);
11     };
12   });
View Code

  這下咱們就知道 this.comparator實際上是一個字符串,而後這個sortBy方法走的是underscore的sortBy方法。

 1 // Sort the object's values by a criterion produced by an iteratee.
 2   _.sortBy = function(obj, iteratee, context) {
 3     iteratee = _.iteratee(iteratee, context);
 4     return _.pluck(_.map(obj, function(value, index, list) {
 5       return {
 6         value: value,
 7         index: index,
 8         criteria: iteratee(value, index, list)
 9       };
10     }).sort(function(left, right) {
11       var a = left.criteria;
12       var b = right.criteria;
13       if (a !== b) {
14         if (a > b || a === void 0) return 1;
15         if (a < b || b === void 0) return -1;
16       }
17       return left.index - right.index;
18     }), 'value');
19   };
View Code

 而underscore的sortBy利用了原生js數組的sort方法,經過先後兩個對象抽出相同屬性比較能夠爲對象數組排序的特性進行排序,只不過實現的方式有些繞彎,有興趣的同窗能夠本身探索一下。原理就是:

1 var arr=[{name:'jack',age:6},{name:'jim',age:4},{name:'tom',age:7}];
2        var o=arr.sort(function(left,right){
3           var a=left.age;
4           var b=right.age;
5           return a-b;
6        });
7        console.log(o);
8       // {name:'jim',age:4} ,{name:'jack',age:6},{name:'tom',age:7}
View Code

 3.at(index)

參數: number  索引值

做用: 獲取該索引下的model-object

原理: 操做collection-object數組

源碼:

1 // Get the model at the given index.
2     at: function(index) {
3       return this.models[index];
4     },
View Code

4.繼承underscore的函數:

繼承的時候,backbone作了一些小小的修改,當執行方法時,underscore中方法所操做的第一個參數obj就是collection-object.models ,第二個參數是咱們在backbone調用時傳的參數

// Underscore methods that we want to implement on the Collection.
  // 90% of the core usefulness of Backbone Collections is actually implemented
  // right here:
  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', 'difference', 'indexOf', 'shuffle',
    'lastIndexOf', 'isEmpty', 'chain'];

  // Mix in each Underscore method as a proxy to `Collection#models`.
  _.each(methods, function(method) {
    Collection.prototype[method] = function() {
      var args = slice.call(arguments);
      args.unshift(this.models);
      return _[method].apply(_, args);
    };
  });

5.clone()
做用:將一個collection-object進行深度克隆

原理:new 一個collection-object出來,須要傳入當前this對象的models

源碼:

1 // Create a new collection with an identical list of models as this one.
2     clone: function() {
3       return new this.constructor(this.models);
4     },
View Code

6.get(obj)

參數:  string/object   能夠是字符串或者{id:''} {cid:''} 組成的對象

做用: 經過id獲取model-object

原理:經過匹配_byId對象,查找相應的模型對象

源碼:

1 // Get a model from the set by id.
2     get: function(obj) {
3       if (obj == null) return void 0;
4       return this._byId[obj.id] || this._byId[obj.cid] || this._byId[obj];
5     },
View Code

 7.where(attrs,first)

參數:attrs object  須要查找的 key-value組合,first bool  是否獲得第一個知足條件的該對象

做用: 根據key-value組合查找到相對應的model-object

原理:若是first存在,則使用繼承自underscore的 find方法遍歷尋找目標對象,不然調用filter遍歷尋找該目標

源碼:

 1 // Return models with matching attributes. Useful for simple cases of
 2     // `filter`.
 3     where: function(attrs, first) {
 4       if (_.isEmpty(attrs)) return first ? void 0 : [];
 5       return this[first ? 'find' : 'filter'](function(model) {
 6         for (var key in attrs) {
 7           if (attrs[key] !== model.get(key)) return false;
 8         }
 9         return true;
10       });
11     },
View Code
相關文章
相關標籤/搜索