http://www.cnblogs.com/dojo-lzz/p/5374218.html @木的樹html
在傳統MVC框架模式中,Model承擔業務邏輯的任務。Backbone做爲一個mvc框架,主要的業務邏輯交由Model與Collection來實現。Model表明領域對象,今天主要學一下Model源碼中幾個重要的函數。前端
咱們先看一下Model的構造函數作了哪些事情:web
// 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的構造函數主要作了如下幾件事:數據庫
接下來是一個重要的set函數,這個函數是Model最核心的一個方法後端
// 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
接下來是與後端打交道的save方法:多線程
// 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方法,源碼以下:mvc
// 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方法作的幾件事:app
接下來是銷燬model的destroy方法:框架
// 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方法作的事情:
與驗證相關的_validate方法以下:
// 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; }