修复附加继承法中模拟重载的一缺陷

    在关于JavaScript继承的一系列讨论中,我详细的介绍和比较了四种继承方式,其中第四种"附加继承法"是一种对重载支持的最好的方法。并且也在我的实际开发中广泛的使用了,不过近来我发现在多层次的重载中,又出现了一个非常严重的bug

    我们使用原来重载一文中的那个grandpa、father & son的示例:

<html>
<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>

    // 在此参考:ExtendsCall方法

    运行结果是三个期望的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  阅读(...)  评论(...编辑  收藏

导航