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('todos.@each.isDone')
});
聚集屬性的魔法是通過@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,
/** @scope Ember.Array.prototype */
{
//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,謝謝!