write by yinmingjun,引用请注明。
如果需要了解ember.js框架,最好的方式是阅读它的代码,作为阅读代码的基础,需要了解ember.js代码的组织形式。ember.js采用的原型法的继承体系,并且在内部维护着类型体系的元数据,并且通过引入观察者的模式来关注各个对象数据的变更,对属性的维护也提供了get/set基础服务。ember的对象体系渗透在ember.js框架的各个环节,组织形式相对复杂,我们作为解读ember.js的开篇,从分析ember.js的对象体系开始入手。
一、class和instance
1、class的定义
在ember.js中,定义一个class是通过父类的extend类方法(请允许我这么表达,是指那些寄存在类的类型上的方法,通过类的类型来引用和调用的方法,下同)来定义的。如果没有父类,可以从Ember.Object开始。
我们看一个例子:
App.Person = Ember.Object.extend({
say: function(thing) {
alert(thing);
}
});
上面这个例子创建了一个App.Person的类,从Ember.Object派生而来。基于这种方式,我们可以从任何一个类派生出新的class。
在派生的类中可以override父类的方法,如果需要访问父类的实现,可以使用特殊的this._supper()方法,调用父类的实现。
例如:
App.Person = Ember.Object.extend({
say: function(thing) {
var name = this.get('name');
alert(name + " says: " + thing);
}
});
App.Soldier = App.Person.extend({
say: function(thing) {
this._super(thing + ", sir!");
}
});
2、创建instance
定义类之后,可以创建类的实例,在ember.js中,创建类的实例是通过调用类的create类方法来创建类的实例。
例如:
var person = App.Person.create();
person.say("Hello") // alerts "Hello"
在使用create方法创建类的实例的时候,可以传递一个object(就是{},一个散列对象表)作为参数,作为实例的属性的初值,建议只传递简单的对象,改写实例的方法还是通过派生类的方式来做。
例如:
Person = Ember.Object.extend({
helloWorld: function() {
alert("Hi, my name is " + this.get('name'));
}
});
var tom = Person.create({
name: "Tom Dale"
});
tom.helloWorld() // alerts "Hi my name is Tom Dale"
3、类实例的初始化
在类的实例创建之后,它的init方法(如果存在)会被自动的调用,这是一个初始化类实例的理想的场所。
例如:
Person = Ember.Object.extend({
init: function() {
var name = this.get('name');
alert(name + ", reporting for duty!");
}
});
Person.create({
name: "Stefan Penner"
});
// alerts "Stefan Penner, reporting for duty!"
使用init方法初始化类的时候,需要注意不要忘记调用父类的init方法(通过this._supper()方法),一些类的实现依赖其init方法,如Ember.View或Ember.ArrayController。
4、类和实例的reopen的概念
在ember.js中,运行类不是一次的定义完毕,运行在其它的地方追加类的定义(动态语言特有的优势),ember.js称呼这种方式为reopen。
我们还是通过例子来看类的reopen。
例子:
Person.reopen({
isPerson: true
});
Person.create().get('isPerson') // true
通过class的reopen类方法,向类追加isPerson属性,并给出true的初值,这样,创建的类的实例马上就可以访问到该属性。
同样,在reopen类方法中,可以override类的方法,并使用this._supper()方法来访问前一个版本的实现。
例如:
PersonBase = Ember.Object.extend({
say: function(thing) {
alert(thing);
}
});
Person = PersonBase.extend({
say: function(thing) {
this._super(">>>"+thing);
}
});
Person.reopen({
// override `say` to add an ! at the end
say: function(thing) {
this._super(thing + "!");
}
});
Person.create().say('hello');
//alert message : '>>>hello!'
reopen中,this._super()是前一个版本的实现,而不是父类的实现,这对改写类的实现很有帮助。
与类的reopen类方法类似的还有reopenClass类方法,这个方法是扩展类型上的方法和属性。
例如:
Person.reopenClass({
createMan: function() {
return Person.create({isMan: true})
}
});
Person.createMan().get('isMan') // true
除了方法,也可以向类型上添加属性,直接通过类型拉访问。
二、类的属性
1、类属性的访问
如果需要访问类的属性,需要通过this.get()和this.set()访问器来操作,直接修改对象的数据成员会绕过ember.js的观察者模式。
例如:
var person = App.Person.create();
var name = person.get('name');
person.set('name', "Tobias Fünke");
2、类的计算属性(Computed Properties)
有些情况下,类的属性值是来自类的其他属性值的计算结果,这种属性成为计算属性,ember.js对计算属性提供了支持。
例如:
Person = Ember.Object.extend({
// these will be supplied by `create`
firstName: null,
lastName: null,
fullName: function() {
var firstName = this.get('firstName');
var lastName = this.get('lastName');
return firstName + ' ' + lastName;
}.property('firstName', 'lastName')
});
var tom = Person.create({
firstName: "Tom",
lastName: "Dale"
});
tom.get('fullName') // "Tom Dale"
计算属性是通过function的property方法来定义的,其返回值就是属性的访问结果。
还有些情况下,还会希望计算属性能设置属性值,可以
Person = Ember.Object.extend({
// these will be supplied by `create`
firstName: null,
lastName: null,
fullName: function(key, value) {
// getter
if (arguments.length === 1) {
var firstName = this.get('firstName');
var lastName = this.get('lastName');
return firstName + ' ' + lastName;
// setter
} else {
var name = value.split(" ");
this.set('firstName', name[0]);
this.set('lastName', name[1]);
return value;
}
}.property('firstName', 'lastName')
});
var person = Person.create();
person.set('fullName', "Peter Wagenet");
person.get('firstName') // Peter
person.get('lastName') // Wagenet
也就是说,对计算属性的set会传递key和value作为参数;get仅会传递key作为参数。
注释1:
由于计算属性可能带来的计算开销,ember.js会缓存计算结果,加速对计算属性的访问。emer.js通过依赖项的变更通知来维护计算属性的缓存,因此需要通过function的property方法来登记依赖的其他属性。
注释2:
估计很多人会希望了解ember.js的计算属性的魔法的细节,稍稍透漏一点,大家可以按图索骥。
function的property方法在ember.js中的代码如下:
Function.prototype.property = function() {
var ret = Ember.computed(this);
return ret.property.apply(ret, arguments);
};
3、类的聚集属性(Aggregate Properties)
严格意义上来说,聚集属性是计算属性的一种,不过大多使用者还是愿意将聚集属性单独做一个分类。
App.todosController = Ember.Object.create({
todos: [
Ember.Object.create({ isDone: false })
],
remaining: function() {
var todos = this.get('todos');
return todos.filterProperty('isDone', false).get('length');
}.property('[email protected]')
});
聚集属性的魔法是通过@each来描述依赖的一个属性的数组,获取数组元素和数组本身的变更、增减,并保持计算结果缓存的有效性。
三、观察者(Observers)模式
在ember.js的任何对象(从Ember.Object派生而来),都可以通过其addObserver方法和addBeforeObserver来添加数据变更的观察者。ember.js类的属性,包括计算属性,都可以添加观察者。
例如:
Person = Ember.Object.extend({
// these will be supplied by `create`
firstName: null,
lastName: null,
fullName: function() {
var firstName = this.get('firstName');
var lastName = this.get('lastName');
return firstName + ' ' + lastName;
}.property('firstName', 'lastName')
});
var person = Person.create({
firstName: "Yehuda",
lastName: "Katz"
});
person.addObserver('fullName', function() {
// deal with the change
});
person.set('firstName', "Brohuda"); // observer will fire
上面的代码对person实例的fullName属性,添加了一个观察change的观察者。
因为观察者在ember.js中非常常见,ember提供了针对class的观察者的书写方式:
Person.reopen({
fullNameChanged: function() {
// this is an inline version of .addObserver
}.observes('fullName')
});
或者,可以借助Ember.observer方法,将上面的代码改写为:
Person.reopen({
fullNameChanged: Ember.observer(function() {
// this is an inline version of .addObserver
}, 'fullName')
});
Ember.addBeforeObserver和Ember.addObserver类似,不同之处是添加变更的前事件。
四、绑定(Bindings)的支持
在ember.js中,将绑定看成两个属性之间的联动,通过观察者来保持数据的一致性。
在ember中,绑定分为双向绑定和单向绑定,我们分别介绍一下。
1、双向绑定(Two-Way Bindings)的支持
最简单的创建双向数据绑定的方式,是创建一个以Binding结尾的属性(在ember中的形式化的文法很常见,有趣),并指定需要绑定的属性的fullName。
例如:
App.wife= Ember.Object.create({
householdIncome: 80000
});
App.husband= Ember.Object.create({
householdIncomeBinding: 'App.wife.householdIncome'
});
App.husband.get('householdIncome'); // 80000
// Someone gets raise.
App.husband.set('householdIncome', 90000);
App.wife.get('householdIncome'); // 90000
上面的代码在App.husband中创建了一个名字为householdIncome的属性(什么名字都可以,以Binding结尾就可以),并建立到App.wife.householdIncome的双向的绑定。这样,在App.husband中操作数据和App.wife中操作数据是一样的。
2、单向绑定(One-Way Bindings)的支持
在ember中,还存在一种绑定模式,变更只是从绑定源头向建立绑定的属性同步变更,而不会将绑定属性的变更同步回绑定的源头,这种方式称为单向绑定。
在ember中使用单向绑定大多是基于性能的原因,如果没什么问题还是应该使用双向的绑定。
例子:
App.user = Ember.Object.create({
fullName: "Kara Gates"
});
App.userView = Ember.View.create({
userNameBinding: Ember.Binding.oneWay('App.user.fullName')
});
// Changing the name of the user object changes
// the value on the view.
App.user.set('fullName', "Krang Gates");
// App.userView.userName will become "Krang Gates"
// ...but changes to the view don't make it back to
// the object.
App.userView.set('userName', "Truckasaurus Gates");
App.user.get('fullName'); // "Krang Gates"
五、一些技术实现的内幕
在前面的介绍中,大家基本上了解了ember.js对象体系的规划,也许会对ember提供的众多特性会感到很兴奋与好奇,想只知道how。接下来在这个章节中,我们会深入解读ember.js源代码,解析ember.js一些关键特性的实现方式,欣赏ember.js在技术领域对javascript社区的贡献。
下面,我们按层次递进,来看看ember.js的内在美。
1、Mixin的概念
先说一下ember.js的Mixin的概念:
Mixin,表示混合,有点类似AOP设计模式,或者C++中的多重继承(Multiple Inheritance)。mixin本身只提供实现,而在类的基础中可以在基础的类之后添加mixin,表示引用mixin中的实现,就像类本身定义的方法一样。
在ember.js中,mixin通过Ember.Mixin.create创建,并支持mixin的继承,最终,在类的定义的时刻将mixin中的方法按次序合并到类中。
我们看看mixin的创建、继承与多重继承:
Ember.Enumerable = Ember.Mixin.create({
//code ……
}) ;
Ember.Array = Ember.Mixin.create(
Ember.Enumerable,
{
//code …….
}
) ;
var NativeArray = Ember.Mixin.create(Ember.MutableArray, Ember.Observable, Ember.Copyable, {
//code ……
});
ember.js的mixin的方式可以很方便的复用代码,值得推荐。
2、Mixin的数据结构
我们看看ember.js的Ember.Mixin是怎么定义的:
Ember.Mixin= function() { return initMixin(this, arguments); };
也就是说,如果执行new Ember.Mixin(),就会转到initMixin方法。
为了统一行为模式,Ember.Mixin也提供了create类型方法:
Mixin.create = function() {
Ember.anyUnprocessedMixins = true;
var M = this;
return initMixin(new M(), arguments);
};
结合前面的代码,可以看明白,Mixin的create方法创建了一个Mixin(无参数),并调用initMixin将参数合并到mixin之中。
看看initMixin的代码可以发现,如果存在参数列表,会将参数放到当前mixin的mixins数据成员之中:
function initMixin(mixin, args) {
if (args && args.length > 0) {
mixin.mixins = a_map.call(args, function(x) {
if (x instanceofMixin) { return x; }
// Note: Manually setup a primitive mixin here. This is the only
// way to actually get a primitive mixin. This way normal creation
// of mixins will give you combined mixins...
var mixin= new Mixin();
mixin.properties = x;
return mixin;
});
}
return mixin;
}
注意标记出来的initMixin对非mixin类型的字典的处理方式。
这样,所有的包含的mixin的对象会形成一个树形的数据结构,关于mixin的组织结构,我们先写到这,后面的mixins的合并,我们在对象创建的过程中来讲解。
3、class Type的构建过程(较长)
这部分我们来看ember.js的继承体系的实现方式,了解ember.js如何维护父子关系,以及如何创建出来第一个基础基类。
在ember.js中,所有类的基类是Ember.Object,在代码中的定义如下:
Ember.Object= Ember.CoreObject.extend(Ember.Observable);
可以注意两点:
(1) 处于简化Object的代码、提高源代码整体的可读性的目的(纯粹是笔者的猜测),ember.js提供了Ember.CoreObject来封装Object的大多功能;
(2) 在Object的级别上,ember.js就将Ember.ObservableMixin合并进来,也就是说,对于一切ember.js对象,都是可观察的;
Ember.Object的大多API都寄存在Ember.CoreObject之上,我们继续追踪Ember.CoreObject的实现。
>>>>>>>>>>>>>Trace CoreObject
Ember.CoreObject的定义:
var CoreObject= makeCtor();
CoreObject.PrototypeMixin = Mixin.create({
reopen: function() {
applyMixin(this, arguments, true);
return this;
},
isInstance: true,
init: function() {},
concatenatedProperties: null,
isDestroyed: false,
isDestroying: false,
destroy: function() {
if (this.isDestroying) { return; }
this.isDestroying = true;
schedule('actions', this, this.willDestroy);
schedule('destroy', this, this._scheduledDestroy);
return this;
},
willDestroy: Ember.K,
_scheduledDestroy: function() {
if (this.isDestroyed) { return; }
destroy(this);
this.isDestroyed = true;
},
bind: function(to, from) {
if (!(from instanceof Ember.Binding)) { from = Ember.Binding.from(from); }
from.to(to).connect(this);
return from;
},
toString: function toString() {
var hasToStringExtension = typeof this.toStringExtension === 'function',
extension = hasToStringExtension ? ":" + this.toStringExtension() : '';
var ret = '<'+this.constructor.toString()+':'+guidFor(this)+extension+'>';
this.toString = makeToString(ret);
return ret;
}
});
CoreObject.PrototypeMixin.ownerConstructor = CoreObject;
CoreObject.__super__= null;
原来的代码比较长,我修整了一下,只提供干货,去掉了注释,便于阅读代码。
CoreObject.PrototypeMixin的作用是提供instance级别的prototype,我们后面会讲,现在先跳过去,知道有这么一个东东就好了。
接下来是makeCtor方法,这个方法中创建了CoreObject类型。为了更好的阅读代码,我讲makeCtor的代码分成了两个部分。
makeCtor方法代码Part I:
function makeCtor() {
var wasApplied = false, initMixins, initProperties;
var Class= function() {
//code ......, see part II
};
Class.toString= Mixin.prototype.toString;
Class.willReopen = function() {
if (wasApplied) {
Class.PrototypeMixin = Mixin.create(Class.PrototypeMixin);
}
wasApplied = false;
};
Class._initMixins= function(args) { initMixins = args; };
Class._initProperties= function(args) { initProperties = args; };
Class.proto= function() {
var superclass= Class.superclass;
if (superclass) { superclass.proto();}
if (!wasApplied) {
wasApplied = true;
Class.PrototypeMixin.applyPartial(Class.prototype);
rewatch(Class.prototype);
}
return this.prototype;
};
return Class;
}
makeCtor方法的目标是从无到有创建出一个class类型,并提供了class的默认构造实现(会在new Class()的时候被调用),为了方便阅读,这部分代码放在makeCtor方法代码Part II中给出。
这部分代码很明确:
> 提供class类型
> 提供class级别的_initMixins和_iniProperties方法,用于设置需要合并的mixin和初始化的参数列表
> 用于重建class.prototype的proto方法
> 提供willReopen类方法,会将当前的ProtoTypeMixin打包到一个mixin之中,很不起眼的一段代码,却是reopen之后可以访问this._supper()方法的关键所在。
makeCtor方法代码Part II:
var Class= function() {
if (!wasApplied) {
Class.proto();// prepare prototype...
}
o_defineProperty(this, GUID_KEY, undefinedDescriptor);
o_defineProperty(this, '_super', undefinedDescriptor);
var m = meta(this);
m.proto = this;
if (initMixins) {
// capture locally so we can clear the closed over variable
var mixins = initMixins;
initMixins = null;
this.reopen.apply(this, mixins);
}
if (initProperties) {
// capture locally so we can clear the closed over variable
var props = initProperties;
initProperties = null;
var concatenatedProperties = this.concatenatedProperties;
for (var i = 0, l = props.length; i < l; i++) {
var properties = props[i];
Ember.assert("Ember.Object.create no longer supports mixing in other definitions, use createWithMixins instead.", !(properties instanceof Ember.Mixin));
for (var keyName in properties) {
if (!properties.hasOwnProperty(keyName)) { continue; }
var value = properties[keyName],
IS_BINDING = Ember.IS_BINDING;
if (IS_BINDING.test(keyName)) {
var bindings = m.bindings;
if (!bindings) {
bindings = m.bindings = {};
} else if (!m.hasOwnProperty('bindings')) {
bindings = m.bindings = o_create(m.bindings);
}
bindings[keyName] = value;
}
var desc = m.descs[keyName];
Ember.assert("Ember.Object.create no longer supports defining computed properties.", !(value instanceof Ember.ComputedProperty));
Ember.assert("Ember.Object.create no longer supports defining methods that call _super.", !(typeof value === 'function' && value.toString().indexOf('._super') !== -1));
if (concatenatedProperties && indexOf(concatenatedProperties, keyName) >= 0) {
var baseValue = this[keyName];
if (baseValue) {
if ('function' === typeof baseValue.concat) {
value = baseValue.concat(value);
} else {
value = Ember.makeArray(baseValue).concat(value);
}
} else {
value = Ember.makeArray(value);
}
}
if (desc) {
desc.set(this, keyName, value);
} else {
if (typeof this.setUnknownProperty === 'function' && !(keyName in this)) {
this.setUnknownProperty(keyName, value);
} else if (MANDATORY_SETTER) {
Ember.defineProperty(this, keyName, null, value); // setup mandatory setter
} else {
this[keyName] = value;
}
}
}
}
}
finishPartial(this, m);
delete m.proto;
finishChains(this);
this.init.apply(this, arguments);
};
大段的代码,看起来有些凌乱,就简单的说明一下其含义吧。
这部分的代码属于class的默认构造方法,在new class()的时候被执行(这个时候已经有class的instance了,所以this会是类的实例)。
在这部分代码中,主要的工作如下:
> 建立类实例的prototype;
> 合并类的mixins;
> 合并类的properties,包括构造properties的元数据和识别properties中绑定的描述;
> finishPartial中,会启动binding的初始化,同步binding的值;
> 关闭prototype的合并;
> 调用类的实例的init方法;
虽然代码量比较大,但是调理比较清晰,赞一个。
看到这里,大家会比较奇怪,没找到CoreObject上有extend方法啊?这部分代码是托管在ClassMixin之中的。为了追根述源,我们继续追踪ClassMixin的代码。
>>>>>>>>>>>>>Trace ClassMixin
到这里,我们已经快到达分析ember.js的类的创建过程的终点了,还是从代码开始:
var ClassMixin= Mixin.create({
ClassMixin: Ember.required(),
PrototypeMixin: Ember.required(),
isClass: true,
isMethod: false,
extend: function() {
var Class= makeCtor(), proto;
Class.ClassMixin= Mixin.create(this.ClassMixin);
Class.PrototypeMixin= Mixin.create(this.PrototypeMixin);
Class.ClassMixin.ownerConstructor= Class;
Class.PrototypeMixin.ownerConstructor= Class;
reopen.apply(Class.PrototypeMixin, arguments);
Class.superclass= this;
Class.__super__ = this.prototype;
proto= Class.prototype= o_create(this.prototype);
proto.constructor= Class;
generateGuid(proto, 'ember');
meta(proto).proto = proto; // this will disable observers on prototype
Class.ClassMixin.apply(Class);
return Class;
},
createWithMixins: function() {
var C = this;
if (arguments.length>0) { this._initMixins(arguments); }
return new C();
},
create: function() {
var C = this;
if (arguments.length>0) { this._initProperties(arguments); }
return new C();
},
reopen: function() {
this.willReopen();
reopen.apply(this.PrototypeMixin, arguments);
return this;
},
reopenClass: function() {
reopen.apply(this.ClassMixin, arguments);
applyMixin(this, arguments, false);
return this;
},
detect: function(obj) {
if ('function' !== typeof obj) { return false; }
while(obj) {
if (obj===this) { return true; }
obj = obj.superclass;
}
return false;
},
detectInstance: function(obj) {
return obj instanceof this;
},
metaForProperty: function(key) {
var desc = meta(this.proto(), false).descs[key];
Ember.assert("metaForProperty() could not find a computed property with key '"+key+"'.", !!desc && desc instanceof Ember.ComputedProperty);
return desc._meta || {};
},
eachComputedProperty: function(callback, binding) {
var proto = this.proto(),
descs = meta(proto).descs,
empty = {},
property;
for (var name in descs) {
property = descs[name];
if (property instanceof Ember.ComputedProperty) {
callback.call(binding || this, name, property._meta || empty);
}
}
}
});
ClassMixin.ownerConstructor= CoreObject;
CoreObject.ClassMixin = ClassMixin;
ClassMixin.apply(CoreObject);
老样子,代码还是干货,去掉了所有的注释。
在ClassMixin的代码中,我们看到了ember.js的大部分核心API,包括我们很关心的extend和create等类方法,这些方法通过Mixin的apply方法,合并到CoreObject上。
为了便于大家分析和研究,将相关的代码贴上:
var MixinPrototype = Mixin.prototype;
MixinPrototype.apply= function(obj) {
return applyMixin(obj, [this], false);
};
其实Mixin的合并一般是在class的创建的过程中来做的,不过对于ClassMixin来说,是需要将Mixin合并到CoreObject类型上,提供类型的方法和属性,因此需要自己来触发Mixin的合并过程。
再回到ClassMixin的extend方法,我们可以看到extend方法的核心使用makeCtor获取一个class type,然后就是根据当前类型(就是super class)的ClassMixin和PrototypeMixin来构造class的ClassMixin和PrototypeMixin(会设置好其层级关系,包含super class的ClassMixin和PrototypeMixin),并设置其父子关系,填充元数据信息,设置类基本的方法和属性等等。
需要注意的是class上的ClassMixin和PrototypeMixin,分别表示类级别方法和实例级别方法的列表,以及在代码中体现出来的reopen的概念,每次reopen之后,都会导致class的PrototypeMixin增加一个层级,这是ember中this._supper()方法的底层的支持上的关键一步。
而ClassMixin的create方法更简单,只是设置类型的启动参数(这里是initProperties),并通过new来触发其默认构造过程的执行。
到这里,对于ember.js的继承和对象的创建过程的分析就先告一段落了,代码很多,但是如果从表达的语义来看是比较简单的。考虑到ember.js基于原型法的继承方式,以及其对观察者模式的内在支持,可以理解ember.js为什么会选择这么复杂的实现。
4、this._super()的实现方式
对ember.js的this._super()方法很喜欢,这在override类的实例方法的时候很实用。
前面写的东东有点多,快到文档字数的极限了,这部分我们就简单点写。
在ClassMixin的extend方法中,通过reopen(MixinPrototype.reopen)将class的PrototypeMixin准备就绪,之后在ClassMixin的create方法中,会触发书写在makeCtor方法中的类的默认的构造方法的执行。在其中,会调用makeCtor为class准备的proto方法,proto方法中将前面整理出来的class的PrototypeMixin的内容,使用Mixin的applyPartial方法(内部会调用applyMixin方法),将其合并到class的prototype之中。具体的代码我就不给出来了,大家自己定位和阅读吧。
在mixin的applyMixin方法中,会通过mergeMixin来合并所有Mixin提供的对象属性(次序是自顶向下的次序,先基类(或称为底层)的Mixin,后派生类(或称为上层)的Mixin),合并的目标是提供的第一个参数,这里是当前类的prototype。而父类的方法,在extend的过程中,会通过Mixin合并到当前class的PrototypeMixin之中。
很明显,在合并mixin中数据的时候,会有一个次序,已经存在的方法被看成superMethod,而接下来合并的同名的方法需要得到他的引用。ember.js是通过Ember.wrap(method, superMethod)来获取一个支持this._supre()的包装函数的。 我们看看其代码:
Ember.wrap= function(func, superFunc) {
function K() {}
function superWrapper() {
var ret, sup = this._super;
this._super= superFunc || K;
ret = func.apply(this, arguments);
this._super= sup;
return ret;
}
superWrapper.wrappedFunction = func;
superWrapper.__ember_observes__ = func.__ember_observes__;
superWrapper.__ember_observesBefore__ = func.__ember_observesBefore__;
return superWrapper;
};
这个包装函数,将一个闭包方法返回来,并引用调用时在调用之前设置this._super,并在调用完毕之后恢复,很聪明的解法。
5、对象属性的get/set过程
ember.js在类实例的级别提供get/set方法,用于获取和设置对象的数据。ember.js支持计算属性,还支持观察者的模式,因此其对属性的管理要复杂一些。我们简单的看看ember.js的属性的读写过程,简单的阐述一下其工作原理。
>>>>>>>>>get 过程:
get= function get(obj, keyName) {
// Helpers that operate with 'this' within an #each
if (keyName === '') {
return obj;
}
if (!keyName && 'string'===typeof obj) {
keyName = obj;
obj = null;
}
Ember.assert("Cannot call get with '"+ keyName +"' on an undefined object.", obj !== undefined);
if (obj === null || keyName.indexOf('.') !== -1) {
return getPath(obj, keyName);
}
var meta = obj[META_KEY], desc = meta && meta.descs[keyName], ret;
if (desc) {
return desc.get(obj, keyName);
} else {
if (MANDATORY_SETTER&& meta && meta.watching[keyName] > 0) {
ret = meta.values[keyName];
} else {
ret= obj[keyName];
}
if (ret === undefined &&
'object' === typeof obj && !(keyName in obj) && 'function' === typeof obj.unknownProperty) {
return obj.unknownProperty(keyName);
}
return ret;
}
};
有几个层次,分别描述一下:
> 如果keyName中存在'.',通过getPath做多路径追踪;
> 如果存在属性的描述,使用属性描述中的get方法获取属性数据;
> 否则,从对象上获取数据。如果数据为undefind,并且在object上有unknownProperty方法,调用unknownProperty方法获取属性值;
>>>>>>>>>set 过程:
var set= function set(obj, keyName, value, tolerant) {
if (typeof obj === 'string') {
Ember.assert("Path '" + obj + "' must be global if no obj is given.", IS_GLOBAL.test(obj));
value = keyName;
keyName = obj;
obj = null;
}
if (!obj || keyName.indexOf('.') !== -1) {
return setPath(obj, keyName, value, tolerant);
}
Ember.assert("You need to provide an object and key to `set`.", !!obj && keyName !== undefined);
Ember.assert('calling set on destroyed object', !obj.isDestroyed);
var meta = obj[META_KEY], desc = meta && meta.descs[keyName],
isUnknown, currentValue;
if (desc) {
desc.set(obj, keyName, value);
} else {
isUnknown = 'object' === typeof obj && !(keyName in obj);
// setUnknownProperty is called if `obj` is an object,
// the property does not already exist, and the
// `setUnknownProperty` method exists on the object
if (isUnknown && 'function' === typeof obj.setUnknownProperty) {
obj.setUnknownProperty(keyName, value);
} else if (meta&& meta.watching[keyName] > 0) {
if (MANDATORY_SETTER) {
currentValue= meta.values[keyName];
} else {
currentValue= obj[keyName];
}
// only trigger a change if the value has changed
if (value !== currentValue) {
Ember.propertyWillChange(obj, keyName);
if (MANDATORY_SETTER) {
if (currentValue === undefined && !(keyName in obj)) {
Ember.defineProperty(obj, keyName, null, value); // setup mandatory setter
} else {
meta.values[keyName] = value;
}
} else {
obj[keyName] = value;
}
Ember.propertyDidChange(obj, keyName);
}
} else {
obj[keyName] = value;
}
}
return value;
};
set方法考虑的东西要多一些,如下:
> 如果keyName中存在'.',通过setPath做路径追踪;
> 如果存在属性的描述,通过其set方法设置属性的值;
> 如果存在属性的观察者,获取属性的旧值,如果与新值不同,将属性的数据存放到对象之中,并分别触发属性变更的前、后事件;
> 如果不存在观察者,直接将属性的值存放到对象上;
6、计算属性的实现
有了前面描述的属性描述的支持,对计算属性的支持也就顺理成章了,我们看看ember.js是怎么做的。
先看看计算属性的定义:
Person = Ember.Object.extend({
// these will be supplied by `create`
firstName: null,
lastName: null,
fullName: function() {
var firstName = this.get('firstName');
var lastName = this.get('lastName');
return firstName + ' ' + lastName;
}.property('firstName', 'lastName')
});
有点类似jquery的语法,写一个函数,并调用函数的property方法返回属性的描述符。
我们看property的实现:
Function.prototype.property= function() {
var ret= Ember.computed(this);
return ret.property.apply(ret, arguments);
};
该方法中,通过Ember.computed方法产生一个ComputedProperty类的实例,并将参数设置到其property之中。
再看Ember.computed方法:
Ember.computed= function(func) {
var args;
if (arguments.length > 1) {
args = a_slice.call(arguments, 0, -1);
func = a_slice.call(arguments, -1)[0];
}
if ( typeof func !== "function" ) {
throw new Error("Computed Property declared without a property function");
}
var cp = new ComputedProperty(func);
if (args) {
cp.property.apply(cp, args);
}
return cp;
};
注释:
从Ember. computed的代码来看,它支持向func中携带参数,如果有参数,最后一个参数是func,前面的是args。
Ember.computed很简单,只是创建一个ComputedProperty类的实例,并返回它。
ComputedProperty类的代码较多,我们只贴一下必要的代码:
function ComputedProperty(func, opts) {
this.func = func;
this._cacheable = (opts && opts.cacheable !== undefined) ? opts.cacheable : true;
this._dependentKeys = opts && opts.dependentKeys;
this._readOnly = opts && (opts.readOnly !== undefined || !!opts.readOnly);
}
Ember.ComputedProperty= ComputedProperty;
ComputedProperty.prototype= newEmber.Descriptor();
var ComputedPropertyPrototype= ComputedProperty.prototype;
ComputedPropertyPrototype.property= function() {
var args = [];
for (var i = 0, l = arguments.length; i < l; i++) {
args.push(arguments[i]);
}
this._dependentKeys = args;
return this;
};
ComputedProperty采用的是原型法的继承方式,直接指定prototype为new Ember.Descriptor(),因此毫无疑问是Descriptor。其定义了Descriptor的get、set等方法,用户控制属性值的获取和设置(代码就不给了),还有提供了volatile和readOnly方法,用来控制计算属性的结果是否缓存,以及是否运行set这个计算属性的值。
六、小结
这里,对ember.js的对象体系结构的描述该结束了,下面对上面描述的ember的核心功能做一个小小的汇总:
> 集成到Ember.Object上的观察者的模式,允许观察每个属性值的变更;
> 通过Mixin提供类型无关的属性和实现,支持AOP的设计模式;
> 提供extend、create等类方法,支持类的派生和类实例的创建;
> 提供reopen的模式,允许单个类在多处定义;
> 提供this._super()方法,可以访问到被override的方法;
> 提供属性描述符(Ember.Descriptor),支持计算属性、别名等复杂属性;
> 对数据绑定的内在支持;
> 提供get/set方法(accessor),接管类的属性值的设置&获取请求;
我对ember.js展示的恢宏的体系结构非常欣赏,很喜欢ember.js的架构师设计ember的方式,也对ember源代码中的优美实现感到很欣喜。读好的代码和读一本好书的感觉类似,优秀的架构师给其注入灵魂和主线;而优美的代码会让每个细节充满了美感。读ember的代码是很快乐的过程,希望ember将来会给我更多的惊喜。
ember,谢谢!