都由於 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._changing
和 this._pending
的使用場景,感受是一個當多個 set 同時執行時候的一個標記位,可是 JS 是單線程執行,裏面又都是 for 語句,按理說能夠不用這兩個標記位。又或者是個人理解有誤。this
看到這,給各類Observer打補丁就有了可行性,支持 Object.defineProperty
就用 Object.defineProperty
,不支持的則降級到走 Backbone 的這種 for in
方式。線程