Backbone原始碼分析(一)
Backbone原始碼分析(二)
Backbone中主要的業務邏輯位於Model和Collection,上一篇介紹了Backbone中的Model,這篇文章中將主要探討Collection的原始碼。
讓我們先來看一下Collection的建構函式:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// 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. var Collection = Backbone.Collection = function(models, options) { options || (options = {}); if (options.model) this.model = options.model;//model物件指定collection管理的Model型別 //comparator 排序使用 if (options.comparator !== void 0) this.comparator = options.comparator; this._reset();// 重置集合 this.initialize.apply(this, arguments);//呼叫初始化方法 //如果引數中傳遞model陣列,則利用models陣列重置集合 if (models) this.reset(models, _.extend({silent: true}, options)); }; |
接下來是在set
, add
, remove
中經常用到的splice
函式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
// Splices `insert` into `array` at index `at`. //將insert陣列在at位置拼接到array中 var splice = function(array, insert, at) { //防止at超出陣列長度 at = Math.min(Math.max(at, 0), array.length); //先建立一個空陣列 var tail = Array(array.length - at); var length = insert.length; var i; //將at之後的陣列元素暫存到tail中 for (i = 0; i < tail.length; i++) tail[i] = array[i + at]; //用insert替換array中at之後的元素 for (i = 0; i < length; i++) array[i + at] = insert[i]; //將原at之後的array元素放到insert元素之後 for (i = 0; i < tail.length; i++) array[i + length + at] = tail[i]; }; |
下面我們來講解set
函式,set
函式是Collection中非常重要的一個函式,集增刪改查與一身,處理了大量的核心業務邏輯:
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 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 |
// 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. set: function(models, options) { if (models == null) return; // var setOptions = {add: true, remove: true, merge: true}; options = _.extend({}, setOptions, options);//setOptions是預設引數 // 如果models為原生物件,會利用Collection中model屬性來轉化成Model例項 if (options.parse && !this._isModel(models)) { models = this.parse(models, options) || []; } var singular = !_.isArray(models); models = singular ? [models] : models.slice(); // 處理at,確保at為合理的數字 var at = options.at; if (at != null) at = +at; //轉化為數字 if (at > this.length) at = this.length; if (at < 0) at += this.length + 1; var set = [];// set表示經過本次處理後應當存在於this.models中的model var toAdd = [];// 本次操作增加的model陣列 var toMerge = [];// 本次操後修改的model陣列 var toRemove = [];// 本次操作刪除掉的models var modelMap = {};//modelMap是本次變化後的應該存在於Collection中的models的key集合 var add = options.add; var merge = options.merge; var remove = options.remove; var sort = false; //有comparator屬性,沒設定at,sort為true //如果對collection做了插入的話,需要自己手動排序 var sortable = this.comparator && at == null && options.sort !== false; //comparator 是model中的屬性 var sortAttr = _.isString(this.comparator) ? this.comparator : null; // Turn bare objects into model references, and prevent invalid models // from being added. var model, i; //先過濾一遍,找出存在於collection中的和不存在於當前collection中的 for (i = 0; i < models.length; i++) {//處理add和existing model = models[i]; // If a duplicate is found, prevent it from being added and // optionally merge it into the existing model. // get根據idAttribute || cid 來查詢 var existing = this.get(model); if (existing) { if (merge && model !== existing) { var attrs = this._isModel(model) ? model.attributes : model; if (options.parse) attrs = existing.parse(attrs, options);//在這之前應該驗證一下 // 使用當前屬性替換model中已存在屬性 existing.set(attrs, options); toMerge.push(existing); // 檢視排序欄位是否有更改 if (sortable && !sort) sort = existing.hasChanged(sortAttr); } // 將更的model id存到modelMap中 if (!modelMap[existing.cid]) { modelMap[existing.cid] = true; set.push(existing); } models[i] = existing; // If this is a new, valid model, push it to the `toAdd` list. } else if (add) { // _prepareModel將原始物件轉化為Model例項 model = models[i] = this._prepareModel(model, options); if (model) { toAdd.push(model); // _addReference 將model加入到Collection的_byId中,並繫結model的所有事件 this._addReference(model, options); modelMap[model.cid] = true; set.push(model); } } } // Remove stale models. if (remove) { for (i = 0; i < this.length; i++) { model = this.models[i]; // 在this.models中但不在本次set中的model,都要刪除 if (!modelMap[model.cid]) toRemove.push(model); } //移除model,this.models、this._byId;移除model的繫結事件 if (toRemove.length) this._removeModels(toRemove, options); } // See if sorting is needed, update `length` and splice in new models. var orderChanged = false; var replace = !sortable && add && remove; if (set.length && replace) {//如果同時有加減操作,便將models放到this.models中 //如果this.models中set的資料不一致,則認為order有變化。 orderChanged = this.length !== set.length || _.some(this.models, function(m, index) { return m !== set[index]; });//沒有啟用排序,但是this.models與set不一致時,仍會觸發sort事件 //處理完remove後,該刪除的都刪除掉;用set替換this.models this.models.length = 0; splice(this.models, set, 0); this.length = this.models.length; } else if (toAdd.length) {//如果僅僅是增加model,則將toAdd插入到指定位置去 if (sortable) sort = true; splice(this.models, toAdd, at == null ? this.length : at); this.length = this.models.length; } // Silently sort the collection if appropriate. //這裡排序一下,但不要觸發事件,在下文統一處理 if (sort) this.sort({silent: true}); // Unless silenced, it's time to fire all appropriate add/sort/update events. //collection跟新完畢後,再傳送事件 // remove在上文刪除時已觸發 if (!options.silent) { for (i = 0; i < toAdd.length; i++) { if (at != null) options.index = at + i; model = toAdd[i]; model.trigger('add', model, this, options); } if (sort || orderChanged) this.trigger('sort', this, options); if (toAdd.length || toRemove.length || toMerge.length) { options.changes = { added: toAdd, removed: toRemove, merged: toMerge }; this.trigger('update', this, options); } } // Return the added (or merged) model (or models). return singular ? models[0] : models; }, |
Collection中儲存model的屬性有兩個:this.models
陣列和_byId
鍵值對,所有的增刪改查都需要對這兩個屬性進行更新。
set函式中主要做了以下幾件事情:
- 將models引數處理成
Model
的例項陣列 - 處理options中的at引數,將其變成一個合理的數字
- 宣告變數
set
,toAdd
,toMerge
,toRemove
,modelMap
- 遍歷models引數,找出其中應當更改或者加入到Collection中的model並新增到上文的變數中
- 從Collection的
this.models
中刪除不存在本次set中的model - 統一更改Collection中的
this.models
陣列 - 排序,但不要觸發sort事件
- 統一處理事件: add、sort、update事件
像add
函式內部就是利用set
函式來處理的:
1 2 3 4 5 6 |
// Add a model, or list of models to the set. `models` may be Backbone // Models or raw JavaScript objects to be converted to Models, or any // combination of the two. add: function(models, options) {//沒有的會加進去,已存在的會根據options決定是否merge return this.set(models, _.extend({merge: false}, options, addOptions)); }, |
繼add
之後,另一個重要的操作就是remove,Collection中的remove邏輯由remove
, _removeModels
, _removeReference
三個函式完成
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
remove: function(models, options) { //處理引數,models處理成陣列 options = _.extend({}, options); var singular = !_.isArray(models); models = singular ? [models] : models.slice(); //刪除掉models,並觸發removed事件 var removed = this._removeModels(models, options); //從Collection層面上觸發update事件 if (!options.silent & removed.length) { options.changes = {added: [], merged: [], removed: removed}; this.trigger('update', this, options);//觸發update事件,注意options裡面的change,這樣可以方便很多事 } return singular ? removed[0] : removed;//注意api的返回值和事件引數的設定 }, |
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 |
// Internal method called by both remove and set. _removeModels: function(models, options) { var removed = []; for (var i = 0; i models.length; i++) { var model = this.get(models[i]); if (!model) continue; // 首先從this.models陣列中刪除model var index = this.indexOf(model); this.models.splice(index, 1); this.length--; // Remove references before triggering 'remove' event to prevent an // infinite loop. #3693 // 從_byId中刪除model的引用 delete this._byId[model.cid]; var id = this.modelId(model.attributes); if (id != null) delete this._byId[id]; // 觸發Collection的remove事件 if (!options.silent) { options.index = index; model.trigger('remove', model, this, options); //被刪除的model也觸發remove事件 } // 刪除model的引用和移除model的繫結事件 removed.push(model); this._removeReference(model, options); } return removed; }, |
1 2 3 4 5 6 7 8 9 10 |
// Internal method to sever a model's ties to a collection. _removeReference: function(model, options) { //斷開model與collection的關聯 delete this._byId[model.cid]; var id = this.modelId(model.attributes); if (id != null) delete this._byId[id]; if (this === model.collection) delete model.collection; // 移除所有的繫結事件 model.off('all', this._onModelEvent, this); }, |
remove整體邏輯如下:
- 處理models引數
- 從
this.models
與_byId
中刪除model,觸發Collection的remove事件 - 斷開model與Collection的關聯,移除model的繫結事件
- 觸發Collection的update事件
與_removeReference
對應的是_addReference
,它的作用於_removeRenence
相反:
1 2 3 4 5 6 7 8 |
// Internal method to create a model's ties to a collection. _addReference: function(model, options) { //將model與collection關聯起來,繫結model的各種事件 this._byId[model.cid] = model; var id = this.modelId(model.attributes); if (id != null) this._byId[id] = model; model.on('all', this._onModelEvent, this); }, |
最後要提一下的是reset函式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// 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. reset: function(models, options) {//reset中不會觸發add和remove事件 options = options ? _.clone(options) : {}; for (var i = 0; i this.models.length; i++) { //斷開與Collection的連結,和移除model的事件 this._removeReference(this.models[i], options); } options.previousModels = this.models;//多看設計 this._reset();//將Collection置空,length、this.models、this._byId models = this.add(models, _.extend({silent: true}, options)); // 觸發事件 if (!options.silent) this.trigger('reset', this, options); return models; }, |