在傳統MVC框架模式中,Model承擔業務邏輯的任務。Backbone作為一個mvc框架,主要的業務邏輯交由Model與Collection來實現。Model代表領域物件,今天主要學一下Model原始碼中幾個重要的函式。
我們先看一下Model的建構函式做了哪些事情:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
// Create a new model with the specified attributes. A client id (`cid`) // is automatically generated and assigned for you. var Model = Backbone.Model = function(attributes, options) { //對引數的處理 var attrs = attributes || {}; options || (options = {}); this.cid = _.uniqueId(this.cidPrefix);//利用underscore生成一個客戶端的唯一識別符號cid this.attributes = {};//this.attributes是backbone中存放所有資料屬性的物件 //collection在獲取model對應的後端url時使用,在model上設定collection並不會自動將model加入collection if (options.collection) this.collection = options.collection; //呼叫parse方法解析資料 if (options.parse) attrs = this.parse(attrs, options) || {}; //處理defaults預設資料,用attrs覆蓋defaults var defaults = _.result(this, 'defaults'); attrs = _.defaults(_.extend({}, defaults, attrs), defaults); this.set(attrs, options);//接收attrs將資料處理後放入this.attributes this.changed = {};//changed屬性用來儲存修改過的屬性資料,第一次set,不需要changed資料 this.initialize.apply(this, arguments);//呼叫initialize初始化model,這個方法需要子類來覆蓋 }; |
Model的建構函式主要做了以下幾件事:
- 處理引數
- 處理model的屬性:cid、attributes、collection
- 解析資料、處理屬性的預設值
- set方法接收處理引數
- 呼叫initialize做初始化操作
接下來是一個重要的set函式,這個函式是Model最核心的一個方法
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 |
// Set a hash of model attributes on the object, firing `"change"`. This is // the core primitive operation of a model, updating the data and notifying // anyone who needs to know about the change in state. The heart of the beast. set: function(key, val, options) { if (key == null) return this; // Handle both `"key", value` and `{key: value}` -style arguments. var attrs; if (typeof key === 'object') {//{key: value}形式 attrs = key; options = val; } else {// key, value, options形式 (attrs = {})[key] = val; } options || (options = {});//設定options引數 // Run validation. //首先驗證引數,這裡沒有直接呼叫validate方法,而是呼叫_validate這個私有方法,該方法內部呼叫validate方法 if (!this._validate(attrs, options)) return false; // Extract attributes and options. var unset = options.unset; var silent = options.silent; var changes = [];//用來存放所有有變動的key var changing = this._changing;//this._chaning用來標識set方法是否在處理中,我猜這裡的設定跟webworker多執行緒有關 this._changing = true;//這裡代表屬性的變動更新開始 // this.changed = {};//不能放在這裡,如果val沒改變,所有changed都被清空掉了 if (!changing) {//使用_previousAttributes來保留最近一次的attributes this._previousAttributes = _.clone(this.attributes); this.changed = {};//每次set時,changed都會被重置的{},這表示僅保留最近一次的變化 } var current = this.attributes; var changed = this.changed; var prev = this._previousAttributes; // For each `set` attribute, update or delete the current value. for (var attr in attrs) {//遍歷attrs val = attrs[attr]; //對於單執行緒環境,current與_previousAttributes是一樣的,這裡的處理也應當是應對多執行緒 if (!_.isEqual(current[attr], val)) changes.push(attr); //changes是本次變化的keys if (!_.isEqual(prev[attr], val)) { changed[attr] = val; //儲存變化量 } else { delete changed[attr]; } //這裡根據unset的設定,如果unset為true移除,否則設定attributes中的對應屬性 unset ? delete current[attr] : current[attr] = val; } // Update the `id`. //idAttribute的目的是跟後端資料庫記錄的id保持一致 if (this.idAttribute in attrs) this.id = this.get(this.idAttribute); // Trigger all relevant attribute changes. // 在所有賦值結束後,傳送事件通知 if (!silent) { if (changes.length) this._pending = options; for (var i = 0; i < changes.length; i++) { this.trigger('change:' + changes[i], this, current[changes[i]], options); } } // You might be wondering why there's a `while` loop here. Changes can // be recursively nested within `"change"` events. if (changing) return this; //這裡我覺得也是跟多執行緒有關,如果多個執行緒同時更新model,最終只發出一個整體的change事件 if (!silent) { while (this._pending) {//很奇怪的設定 options = this._pending; this._pending = false; this.trigger('change', this, options);//觸發事件 } } this._pending = false; this._changing = false; return this; } |
來整理一下set方法做的幾件事:
- 根據api的引數宣告來處理引數
- 宣告幾個與屬性變化相關的變數
- 設定_previousAttributes與changed來儲存上次屬性和這次的變化資料
- 更新屬性,儲存本次變化資料和對應的key
- 將發生變化的屬性廣播出去,change:key形式
- 在model層次上發出change事件
接下來是與後端打交道的save方法:
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 |
// Set a hash of model attributes, and sync the model to the server. // If the server returns an attributes hash that differs, the model's // state will be `set` again. // save方法保持客戶端與資料庫內記錄同步,前後端資料可能出現不一致情況, // 如果options中wait引數為true的話,會用後端返回的資料來更新前端資料 save: function(key, val, options) { // Handle both `"key", value` and `{key: value}` -style arguments. var attrs; if (key == null || typeof key === 'object') {//`{key: value}` attrs = key; options = val; } else {//`"key", value` (attrs = {})[key] = val; } //在方法預設開啟驗證和解析 options = _.extend({validate: true, parse: true}, options); var wait = options.wait; // If we're not waiting and attributes exist, save acts as // `set(attr).save(null, opts)` with validation. Otherwise, check if // the model will be valid when the attributes, if any, are set. // wait為false的話,首先在前端更新model,set中呼叫驗證方法 if (attrs && !wait) { if (!this.set(attrs, options)) return false; } else if (!this._validate(attrs, options)) {//否則利用_validate進行驗證 return false; } // After a successful server-side save, the client is (optionally) // updated with the server-side state. var model = this;//儲存this關鍵字 var success = options.success; var attributes = this.attributes; options.success = function(resp) { // Ensure attributes are restored during synchronous saves. model.attributes = attributes;//這裡先確保資料與為同步時保持一致 var serverAttrs = options.parse ? model.parse(resp, options) : resp; // wait屬性為true,利用後端資料更新model的屬性 if (wait) serverAttrs = _.extend({}, attrs, serverAttrs); if (serverAttrs && !model.set(serverAttrs, options)) return false; // success帶表更新成功後的回撥函式。 // save方法,將模型資料的同步完全封裝起來,開發者只需專注於自身業務邏輯即可! if (success) success.call(options.context, model, resp, options); // 觸發sync事件 model.trigger('sync', model, resp, options); }; wrapError(this, options); // Set temporary attributes if `{wait: true}` to properly find new ids. //wait 為true,臨時更新attributes,目的是下文中將model更新到資料庫內 if (attrs && wait) this.attributes = _.extend({}, attributes, attrs); //根據model是否擁有idAttribute屬性,決定是建立還是更新 var method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update'); if (method === 'patch' && !options.attrs) options.attrs = attrs; var xhr = this.sync(method, this, options); // Restore attributes. this.attributes = attributes;//恢復資料,等到success後利用後端資料結果更新屬性 return xhr; }, |
其中用到的wrapError方法,原始碼如下:
1 2 3 4 5 6 7 8 9 |
// Wrap an optional error callback with a fallback error event. //將options中的error回撥函式,變成一個能夠觸發error事件的回撥函式 var wrapError = function(model, options) { var error = options.error; options.error = function(resp) { if (error) error.call(options.context, model, resp, options); model.trigger('error', model, resp, options); }; } |
save方法做的幾件事:
- 處理引數
- 如果以客戶端為準,則首先跟新model,否則驗證需儲存的屬性
- 宣告區域性變數,替換options中的success回撥函式和error回撥
- 如果以後端返回資料為準,則先直接將attributes屬性暫時更改,方便sync方法同步model,而後將attributes恢復,等待succes毀掉中利用後端返回結果更新
接下來是銷燬model的destroy方法:
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 |
// Destroy this model on the server if it was already persisted. // Optimistically removes the model from its collection, if it has one. // If `wait: true` is passed, waits for the server to respond before removal. //destroy方法用來銷燬model,當wait屬性為true時,等待後臺銷燬成功時做實際銷燬工作 destroy: function(options) { options = options ? _.clone(options) : {}; var model = this; var success = options.success; var wait = options.wait; var destroy = function() { model.stopListening();//移除其他程式碼中監聽的model事件 // 觸發destroy事件 model.trigger('destroy', model, model.collection, options); }; // 後臺銷燬成功後的success回撥 options.success = function(resp) { if (wait) destroy();//銷燬操作 // 回撥函式,業務邏輯相關 if (success) success.call(options.context, model, resp, options); //擁有idAttribute屬性,則觸發sync事件 if (!model.isNew()) model.trigger('sync', model, resp, options); }; var xhr = false; if (this.isNew()) {//資料庫中並沒有該條記錄 _.defer(options.success);//underscore函式,延遲呼叫function直到當前呼叫棧清空為止 } else { wrapError(this, options);//包裝錯誤 xhr = this.sync('delete', this, options);// 與後臺資料同步 } if (!wait) destroy(); //無需後臺等待的話,直接做銷燬操作 return xhr; } |
destroy方法做的事情:
- 宣告區域性變數以及做銷燬操作的destroy方法
- 替換options中的success方法
- 如果model未儲存於資料庫中,直接使用underscore的defer延遲執行success,否則向後臺傳送刪除請求
與驗證相關的_validate方法如下:
1 2 3 4 5 6 7 8 9 10 11 12 |
// Run validation against the next complete set of model attributes, // returning `true` if all is well. Otherwise, fire an `"invalid"` event. _validate: function(attrs, options) { if (!options.validate || !this.validate) return true; attrs = _.extend({}, this.attributes, attrs); //backbone希望在驗證失敗時候,validate方法返回一個error物件 var error = this.validationError = this.validate(attrs, options) || null; if (!error) return true; //觸發invalid事件,也就是說如果單獨呼叫validate方法不會觸發invalid事件 this.trigger('invalid', this, error, _.extend(options, {validationError: error})); return false; } |