Backbone源碼研究 - Backbone.Model

前言

都由於 IE8 不支持 Object.defineProperty,可是業務還不能脫離 IE7 和 IE8,故研究下 Backbone.Model 的實現機制,找機會給主流的 MVVM 框架補丁javascript

僞代碼

先來看看 Model 的構造函數html

var Model = Backbone.Model = function(attributes, options) {
    var attrs = attributes || {};
    options || (options = {});
    
    // 鉤子
    this.preinitialize.apply(this, arguments);
    
    // 每一個實例分配一個惟一的 cid
    this.cid = _.uniqueId(this.cidPrefix);
    // 數據對象
    this.attributes = {};
    
    // 不重要的內容
    if (options.collection) this.collection = options.collection;
    if (options.parse) attrs = this.parse(attrs, options) || {};
    
    // 獲取預設在 defaults 字段中的初始鍵值對或匿名函數
    // 這裏使用 _.result() 來兼容函數和對象兩種類型
    var defaults = _.result(this, 'defaults');
    
    // 避免 attrs 中的 undefined 值覆蓋掉 defaults 中的默認值
    attrs = _.defaults(_.extend({}, defaults, attrs), defaults);
    
    // 初始化賦值
    this.set(attrs, options);
    this.changed = {};
    
    // 鉤子
    this.initialize.apply(this, arguments);
  };

很簡單的代碼,作了一些初始化賦值的事情。java

用到了一個小技巧 attrs = _.defaults(_.extend({}, defaults, attrs), defaults); 來防止誤傳入的 undefined 覆蓋掉默認的 defaults 值。app

Backbone 的精粹都在 set(){} 這個函數裏面。框架

set: function(key, val, options) {
  if (key == null) return this;

  // 統一 'key', 'val' 和 {key:val} 這兩種形式
  // attrs 最終爲變更的對象
  var attrs;
  if (typeof key === 'object') {
    attrs = key;
    options = val;
  } else {
    (attrs = {})[key] = val;
  }

  options || (options = {});

  // 規則驗證.
  if (!this._validate(attrs, options)) return false;

  var unset      = options.unset;
  var silent     = options.silent;
  var changes    = [];
  var changing   = this._changing;
  this._changing = true;

  // 預留上一次的值
  if (!changing) {
    this._previousAttributes = _.clone(this.attributes);
    this.changed = {};
  }

  // 備份一個當前的數據對象
  var current = this.attributes;
  var changed = this.changed;
  var prev    = this._previousAttributes;

  // 遍歷傳入的新數據對象
  for (var attr in attrs) {
    val = attrs[attr];
    
    // 若是新數據與當前不一致,則標記變更的鍵名
    if (!_.isEqual(current[attr], val)) changes.push(attr);
    
    // 若是新數據與舊數據不一致,則更新已變更的數據備份
    if (!_.isEqual(prev[attr], val)) {
      changed[attr] = val;
    } else {
      delete changed[attr];
    }
    
    // 真正幹活的代碼,更新數據或者刪除數據
    unset ? delete current[attr] : current[attr] = val;
  }

  // 更新 id 字段
  if (this.idAttribute in attrs) this.id = this.get(this.idAttribute);

  if (!silent) {
    if (changes.length) this._pending = options;
    
    // 遍歷變更清單,而且逐個觸發 `change:` 事件
    for (var i = 0; i < changes.length; i++) {
      this.trigger('change:' + changes[i], this, current[changes[i]], options);
    }
  }

  if (changing) return this;
  if (!silent) {

    // 觸發一個總的 `change` 事件
    // 註釋說這裏用 while 是確保嵌套場景也只觸發一個 `change` 事件
    while (this._pending) {
      options = this._pending;
      this._pending = false;
      this.trigger('change', this, options);
    }
  }
  this._pending = false;
  this._changing = false;
  return this;
},

整個 set 裏面,實際幹活的就是 unset ? delete current[attr] : current[attr] = val;函數

沒看明白 this._changingthis._pending 的使用場景,感受是一個當多個 set 同時執行時候的一個標記位,可是 JS 是單線程執行,裏面又都是 for 語句,按理說能夠不用這兩個標記位。又或者是個人理解有誤。this

more

看到這,給各類Observer打補丁就有了可行性,支持 Object.defineProperty 就用 Object.defineProperty,不支持的則降級到走 Backbone 的這種 for in 方式。線程

同步更新博客

https://www.mxgw.info/a/backb...code

相關文章
相關標籤/搜索