原文: ASP.NET AJAX In-Depth: Object Inheritance 作者:Stephen Walther
来源:http://www.cnblogs.com/dflying/archive/2008/03/16/1108763.html
---------------------------------------------------------------------
本文的目的很简单 : 就是在享用ASP.NET AJAX框架对象继承体系带来的好处同时,我想弄清在这鼓里究竟埋了什么东东。 让我们从这段简单的代码开始 。。。
// 代码1 – AjaxInheritance.aspx
1: <script type="text/javascript">
2:
3: function BaseControl()
4: {
5: this._propA = "BaseControl propA";
6: this._propB = "BaseControl propB";
7: }
8:
9: BaseControl.prototype =
10: {
11: get_propA : function() {return this._propA;},
12: set_propA : function(value) {this._propA = value; },
13:
14: get_propB : function() {return this._propB; },
15: set_propB : function(value) {this._propB = value; }
16: }
17:
18: BaseControl.registerClass("BaseControl");
19:
20:
21: function TreeViewControl()
22: {
23: TreeViewControl.initializeBase(this);
24: this._propA = "TreeViewControl propA";
25: }
26:
27: TreeViewControl.prototype =
28: {
29: get_propA : function() {return this._propA;},
30: set_propA : function(value) {this._propA = value; }
31: }
32:
33: TreeViewControl.registerClass("TreeViewControl", BaseControl);
34:
35: var treeView1 = new TreeViewControl();
36:
37: alert( treeView1.get_propA() ); // displays "TreeViewControl propA"
38: alert( treeView1.get_propB() ); // displays "BaseControl propB"
39:
40:
41: </script>
上面的这段代码定义了BaseControl和TreeViewControl两个对象。他们兴许代表了web页里展示的两个对象。TreeViewControl继承自BaseControl。
首先,BaseControl定义了一个名为BaseControl的构造函数。构造函数初始化了名为_propA和_propB的两个变量。接下来,用以提供向外界提供访问私有变量_propA,_propB的途径,set,get方法被作为公开属性添加到BaseControl的原型中。最后,利用ASP.NET AJAX客户端框架提供的registerClass方法,将BaseControl对象注册为一个类。在稍后我们将讨论调用registerClass方法其中的细节。
其后,定义一个新对象TreeViewControl。大家注意到,TreeViewControl的构造函数里调用了一个名为initializeBase的ASP.NET AJAX方法。后文同样会就这个方法进行讨论。
类似BaseControl,TreeViewControl通过调用registerClass注册为一个类,但是TreeViewControl确是作为BaseControl的子类.传递到registerClass方法中的第二个参数能够让你指定父类
最后,创建了名为treeView1的TreeViewControl实例,get_propA() 和get_propB() 方法被调用并显示出测试结果
理解registerClass()方法
为了探究上文中的细节,我们第一个要了解的ASP.NET AJAX方法就是registerClass.它接收以下三个参数
· typeName – 注册的类名
· baseType – 被注册类的父类 [ 可选 ]
· interfaceTypes – 被注册类需要实现的接口列表 [ 可选 ]
通过参阅Microsoft Ajax框架中的MicrosoftAjax.debug.js文件你能够将registerClass其中细节一览无余.
下面截取的代码片段向我们展示了registerClass方法的相关信息
// 代码2 – MicrosoftAjax.debug.js registerClass() method
1: Type.prototype.registerClass = function(typeName, baseType, interfaceTypes) {
2:
3: var parsedName;
4: try {
5: parsedName = eval(typeName);
6: }
7: catch(e) {
8: throw Error.argument('typeName', Sys.Res.argumentTypeName);
9: }
10: if (parsedName !== this) throw Error.argument('typeName', Sys.Res.badTypeName);
11: if (Sys.__registeredTypes[typeName]) throw Error.invalidOperation(String.format(Sys.Res.typeRegisteredTwice, typeName));
12: if (baseType && !baseType.__class) throw Error.argument('baseType', Sys.Res.baseNotAClass);
13: this.prototype.constructor = this;
14: this.__typeName = typeName;
15: this.__class = true;
16: if (baseType) {
17: this.__baseType = baseType;
18: this.__basePrototypePending = true;
19: }
20: Sys.__upperCaseTypes[typeName.toUpperCase()] = this;
21:
22: Sys.__registeredTypes[typeName] = true;
23: return this;
24: }
在上面的代码中你首先需要注意的就是registerClass是一个名为"Type"对象的方法(它定义在Type原型中).那Type是一个什么东东呢?原来它只是JavaScript中Function的别名而已. Microsoft AJAX框架用以这段代码创建别名:
window.Type = Function;
这样一来,我们就能够在任何function中调用registerClass方法了,通常来说,你应该在构造函数中调用它.
从下面的代码开始,让我们步步深入,探究registerClass的其中种种.
1: var parsedName;
2: try {
3: parsedName = eval(typeName);
4: }
5: catch(e) {
6: throw Error.argument('typeName', Sys.Res.argumentTypeName);
7: }
8: if (parsedName !== this) throw Error.argument('typeName', Sys.Res.badTypeNam
当你调用registerClass方法时,第一个字符串型参数应该是当前调用者的对象名. 用以下你能够调用registerClass方法注册名为TreeViewControl的类:
TreeViewControl.registerClass(“TreeViewControl”);
这样捣鼓看起来是不是显得有些多余?为什么需要引用TreeViewControl两次呢:一次是作为对象,一次是字符串形式. 我认为这样做的目的是为了获取字符串形式的函数名,用以校验赋予的string值是否等价于真实的参数. 有一种方法,我们可以避免类似的冗余:将要注册的函数内调用toString()方法来检验函数名 :
this.toString().match(/( \w+)/)[0]
我推测,Microsft AJAX没有这样做的原因可能是出于性能考虑.
接下来的代码
1: if (Sys.__registeredTypes[typeName]) throw Error.invalidOperation(String.format(Sys.Res.typeRegisteredTwice, typeName));
2: if (baseType && !baseType.__class) throw Error.argument('baseType', Sys.Res.baseN
第一行代码能够防止同一类名被多次注册,第二行则验证传入的父类参数是否实际存在.
this.prototype.constructor = this;
这行代码很好很有趣.它fix了Javascript中constuctor属性的bug,如下
// 代码3 – BadConstructor.js
1: <script type="text/javascript">
2:
3: // Bad Constructor
4: function A() {}
5: function B() {}
6:
7: B.prototype = new A();
8:
9: var x = new B();
10: alert( x.constructor ); // Returns A
11:
12:
13: // Good Constructor
14: function A() {}
15: function B() {}
16:
17: B.prototype = new A();
18: B.prototype.constructor = B; // fix constructor
19:
20: var x = new B();
21: alert( x.constructor ); // Returns B
22:
23:
24: </script>
如上,构造函数应该返回当前构造对象的函数名.不幸的是,因为prototype chain的存在,constructor属性返回了错误的构造函数,它返回的是构造prototype chain顶部对象的函数名称.当[代码3]中的x.constructor第一次被调用,其返回了一个错误值.经过后面的处理,x.constructor才返回了正确值.
1: this.__typeName = typeName;
2: this.__class = true;
3: if (baseType) {
4: this.__baseType = baseType;
5: this.__basePrototypePending = true;
6: }
7: Sys.__upperCaseTypes[typeName.toUpperCase()] = this;
8:
9: Sys.__registeredTypes[typeName] = true;
在这里,为当前构造函数添加了一些属性:__typeName, __class,__baseType, 和 __basePrototypePending.这些属性会被一些方法例如initializeBase()进行反射处理,详情稍后讨论.
最后,构造函数名被Sys命名空间中的__upperCaseTypes 和__registeredTypes数组所保存.__upperCaseTypes被Type.parse方法用递对象名来生成对象实例.__registeredTypes数组则被registerClass方法用来确认类没有被重复注册.你需要注意到的是registerClass方法并没有修改prototype属性,直到initializeBase的出现....
理解 initializeBase 方法
你可以在派生类的构造函数中调用initializeBase方法.例如在[代码1]中,我们在TreeViewControl的构造函数中调用initializeBase方法.如果你不这么做,派生类将不能从其父类中继承到任何东东.
1: Type.prototype.initializeBase = function(instance, baseArguments) {
2: if (!this.isInstanceOfType(instance)) throw Error.argumentType('instance', Object.getType(instance), this);
3: this.resolveInheritance();
4: if (this.__baseType) {
5: if (!baseArguments) {
6: this.__baseType.apply(instance);
7: }
8: else {
9: this.__baseType.apply(instance, baseArguments);
10: }
11: }
12: return instance;
13: }
和registerClass方法一样,initializeBase也被创建为Function对象的方法(Function对象在Microsoft Ajax中的别名为Type). 这样你就能在任何function中调用initializeBase.这样做的意义在于你能够在当前function或当前function的父类的构造函数中调用initializeBase
if (!this.isInstanceOfType(instance))
throw Error.argumentType('instance', Object.getType)
这段代码验证了传递到initializeBase方法中的实例参数是否和当前调用者(或是当前调用者的父类)相同.如下
// 代码5 – BadInitializeBase.js
1: <script type="text/javascript">
2:
3: function A() {}
4: A.registerClass("A");
5: function B() {}
6: B.registerClass("B");
7: function C()
8: {
9: A.initializeBase(this); // throws exception
10: B.initializeBase(this); // ok
11: C.initializeBase(this); // ok
12: }
13: C.registerClass("C", B);
14:
15: var x = new C();
16:
17:
18: </script>
注意,下面这段非常重要!
this.resolveInheritance();
通过调用resolveInheritance方法修改了当前对象的原型
// 代码 6 – MicrosoftAjax.debug.js resolveInheritance()
1: Type.prototype.resolveInheritance = function() {
2: if (this.__basePrototypePending) {
3: var baseType = this.__baseType;
4: baseType.resolveInheritance();
5: for (var memberName in baseType.prototype) {
6: var memberValue = baseType.prototype[memberName];
7: if (!this.prototype[memberName]) {
8: this.prototype[memberName] = memberValue;
9: }
10: }
11: delete this.__basePrototypePending;
12: }
13: }
resolveInheritance方法检验构造函数的__basePrototypePending属性是否为真.当你注册一个新的类时,registerClass方法会将__basePrototypePending设置为true.
接下来,resolveInheritance方法会被继承链中每个对象的构造函数重复调用.自顶向下,继承链中每个对象的属性被依次拷贝.
// 代码 7 – AjaxInheritance.js
1: <script type="text/javascript">
2:
3: function BaseControl()
4: {
5: this._propA = "BaseControl propA";
6: this._propB = "BaseControl propB";
7: }
8:
9: BaseControl.prototype =
10: {
11: get_propA : function() {return this._propA;},
12: set_propA : function(value) {this._propA = value; },
13:
14: get_propB : function() {return this._propB; },
15: set_propB : function(value) {this._propB = value; }
16: }
17:
18: BaseControl.registerClass("BaseControl");
19:
20:
21: function TreeViewControl()
22: {
23: TreeViewControl.initializeBase(this);
24: this._propA = "TreeViewControl propA";
25: }
26:
27: TreeViewControl.prototype =
28: {
29: get_propA : function() {return this._propA;},
30: set_propA : function(value) {this._propA = value; }
31: }
32:
33: TreeViewControl.registerClass("TreeViewControl", BaseControl);
34:
35: var treeView1 = new TreeViewControl();
36:
37: alert( treeView1.get_propA() ); // displays "TreeViewControl propA"
38: alert( treeView1.get_propB() ); // displays "BaseControl propB"
39:
40:
41: </script>
在TreeViewControl的构造函数中调用initializeBase方法时,resolveInheritance方法亦被调用.resolveInheritance方法检索TreeViewControl的父类对象.在这里,TreeViewControl的父类是BaseControl,BaseControl的每一个属性都被拷贝到TreeViewControl的constructor原型中.
为什么当前对象原型的属性只能从父类中拷贝呢? 换言之,why are theprototypes flattened? 出于性能原因? 如果原型链中的所有属性都拷贝到最底层中,那么就没有必要解析原型链中的全部属性.
在FireFox中,下面表达式返回true
alert( treeView1.__proto__.hasOwnProperty("get_propB") ); // displays true
__proto__ property是FireFox的专有属性,表示了当前对象的原型.hasOwnProperty方法只有当一个属性为对象的本身属性而不是从原型链中继承时才返回true.上述代码显示了该原型已被flattened并且读取该属性已脱离原型链.
initializeBase方法执行的最后一段代码如下
1: if (this.__baseType) {
2: if (!baseArguments) {
3: this.__baseType.apply(instance);
4: }
5: else {
6: this.__baseType.apply(instance, baseArguments);
7: }
8: }
这段代码检验了当前的构造函数是否通过registerClass方法指定父类.如果存在父类,apply方法将父类已创建的对象传递给当前的构造函数.apply方法展示了treeView1对象是如何获得_propA和_propB对象的.