修复附加继承法中模拟重载的一缺陷
在关于JavaScript继承的一系列讨论中,我详细的介绍和比较了四种继承方式,其中第四种"附加继承法"是一种对重载支持的最好的方法。并且也在我的实际开发中广泛的使用了,不过近来我发现在多层次的重载中,又出现了一个非常严重的bug。
我们使用原来重载一文中的那个grandpa、father & son的示例:
<head>
<title>Researh Override in JavaScript OOP II</title>
<meta name="author" content="birdshome@博客园" />
</head>
<body>
<script language="javascript"></script>
<script language="javascript">
var g = new grandpa('Michael', 'William');
alert(g.getName());
var f = new father('John', 'William');
alert(f.getName());
var s = new son('Tom', 'William');
alert(s.getName());
</script>
</body>
</html>
// 在此参考:Extends、Call方法
运行结果是三个期望的alert窗口:
No.1 alert:
grandpa's name: Michael
No.2 alert:
grandpa's name: John
father's name: John
No.3 alert:
grandpa's name: Tom
father's name: Tom
son's name: Tom
这时如果我们修改上面的示例,把son对象的getName()方法注释掉,再次运行上面的代码,这时IE就Stack Overflow并立即crash掉了。为什么会这样呢?
在重载(续)一文中,我们详细的介绍了Call的实现原理,以及它是怎么样去查找base类中的方法的。而问题就出在这个查找方法所属的类实例的这个过程中,当son对象没有getName()方法时,我们调用s.getName()实际已经就是调用的father的getName()方法,这时在Call方法的检索循环中出现如下值:
由于我们没有给son类实现getName()方法,在调用s.getName()的时候,执行查找方法的所在类的时候。如果条件: thatBase[key] == arguments.callee.caller 为true,那么会出现:thatBase.getName和thatBase.base.getName实际是同一个方法的引用的情况,这是因为son没有实现getName(),thatBase.getName就是father的getName,然后thatBase.base实际是father的实例,所以thatBase.getName和thatBase.base.getName是同一个方法的引用。从上图的wathc 1窗口中也可以看到使用toString()输出的是同一个方法的内容。
修复这个bug的方法需要继续读取继承树,即:target[key] == target.base[key] 时,让target = target.base;,修改后的源代码为:
CommonLab JScript Code Library
Author: lizhi[at]hit.edu.cn[http://www.cnblogs.com/birdshome/]
Company: http://www.u-soft.com.cn/
Copyright: U-Soft Co., Ltd. © All rights reserved.
Version: 1.2
Created: 2004.11.30 22:27
Updated: 2005.03.25 14:17
History:
8. Attach 'Call' method to Function. [2005.02.23]
Announce: You may use the code freely if this comment reserved.
**********************************************************/
Function.prototype.Call = function(that)
{
if ( arguments.length < 1 )
{
throw new Error('The first parameter must be swapped object.');
}
var thatBase = that;
do
{
for ( var key in thatBase )
{
if ( thatBase[key] == arguments.callee.caller )
{
var target = thatBase;
while(target[key] == target.base[key])
{
target = target.base;
}
if ( arguments.length == 1 )
{
return target.base[key].call(that);
}
else
{
var args = [];
for ( var i=1 ; i < arguments.length ; ++i )
{
args.push(arguments[i]);
}
return target.base[key].apply(that, args);
}
}
}
}
while ( thatBase = thatBase.base )
};
这样一来,我们就可以通过重载调用任意级别基类的方法了。
posted on 2005-04-14 23:39 birdshome 阅读(2182) 评论(4) 编辑 收藏 举报