前言
都因为 IE8 不支持 Object.defineProperty
,但是业务还不能脱离 IE7 和 IE8,故研究下 Backbone.Model 的实现机制,找机会给主流的 MVVM 框架补丁
伪代码
先来看看 Model 的构造函数
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);
};
很简单的代码,做了一些初始化赋值的事情。
用到了一个小技巧 attrs = _.defaults(_.extend({}, defaults, attrs), defaults);
来防止误传入的 undefined 覆盖掉默认的 defaults 值。
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 语句,按理说可以不用这两个标记位。又或者是我的理解有误。
more
看到这,给各种Observer打补丁就有了可行性,支持 Object.defineProperty
就用 Object.defineProperty
,不支持的则降级到走 Backbone 的这种 for in
方式。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。