Backbone源碼分析(二)

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的構造函數主要作了如下幾件事:數據庫

  • 處理參數
  • 處理model的屬性:cid、attributes、collection
  • 解析數據、處理屬性的默認值
  • set方法接收處理參數
  • 調用initialize作初始化操做

  

  接下來是一個重要的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

  • 根據api的參數聲明來處理參數
  • 聲明幾個與屬性變化相關的變量
  • 設置_previousAttributes與changed來保存上次屬性和此次的變化數據
  • 更新屬性,保存本次變化數據和對應的key
  • 將發生變化的屬性廣播出去,change:key形式
  • 在model層次上發出change事件

  

  接下來是與後端打交道的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,不然驗證需保存的屬性
  • 聲明局部變量,替換options中的success回調函數和error回調
  • 若是之後端返回數據爲準,則先直接將attributes屬性暫時更改,方便sync方法同步model,然後將attributes恢復,等待succes毀掉中利用後端返回結果更新

  

  接下來是銷燬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方法作的事情:

  • 聲明局部變量以及作銷燬操做的destroy方法
  • 替換options中的success方法
  • 若是model未存儲於數據庫中,直接使用underscore的defer延遲執行success,不然向後臺發送刪除請求

 

  與驗證相關的_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;
    }
複製代碼
相關文章
相關標籤/搜索