JavaScrip是一个无类、面向对象的语言。因为如此,它采用了原型的继承方式取代了传统的继承方式。这就会使那些接受传统面向对象语言(像C++,Java)教育的程序员们迷惑了。JavaScript的原型继承方式使它比传统的继承方式具有更强的表现力。这点我们马上就要看到了。øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
[tr=thistle][tr=white][tr=white][tr=white][tr=white][tr=white][tr=white]
JavaøÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
JavaScriptøÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
强类型øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
弱类型øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
静态的øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
动态的øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
传统的继承øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
原型的方式øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
函数øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
构造器øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
函数øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
方法øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
函数øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
但是首先,为什么我们这么在意继承呢?有两个主要原因。第一,类型转换。语言系统能自动地将实例转换成相似的类型。JavaScript里对象的实例从来就不需要强制转换。第二,代码复用。写出很多重复实现相同方法的对象是非常常见的。而类就可以实现同样的效果而只需定义函数一次。拥有很多类似的对象也是非常常见的,这些对象只是在方法的个数和参数上有些细微的不同。传统的继承方式对解决这个问题非常有用,但是原型继承方式比这更为优雅。为了证明这点,我将用类似于传统面向对象语言的方式写一个"小玩意儿",然后我将展示一些在传统面向对象语言中没有但非常有用的方式。最后,我将解释这个"小玩意儿"。 øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
传统继承 øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
首先,我将写一个名叫Parenizor的类。它有set和get方法去获取类的值,一个toString方法把它的值包裹在一对小括号里面然后返回øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
function Parenizor(value) øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
{ øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
    this.setValue(value); øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
} øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
Parenizor.method('setValue', function (value) øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
{ øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
    this.value = value; øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
    return this; øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
}); øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
Parenizor.method('getValue', function () øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
{øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
    return this.value; øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
}); øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
Parenizor.method('toString', function () øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
{øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
    return '(' + this.getValue() + ')'; øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
}); øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
这个语法有点不同寻常,但很容易就会发现里面有面向对象语言的痕迹。method方法需要一个方法名和一个函数两个参数,它将传入的方法名称添加为类的一个公共方法。 øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
我们可以这样使用: øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
myParenizor = new Parenizor(0); øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
myString = myParenizor.toString(); øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
myString的值为:"(0)",将值0包裹在一对小括号里面。 øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
现在我要写另外一个类,它继承自Parenizor。它的toString方法和Parenizor类的toString方法不一样。如果值为0或为空,则toString方法就返回"-0-"; øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
function ZParenizor(value) øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
{øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
    this.setValue(value); øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
} øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
ZParenizor.inherits(Parenizor); øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
ZParenizor.method('toString', function () øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
{øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
    if (this.getValue()) øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
    {øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
        return this.uber('toString'); øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
    } øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
    return "-0-"; øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
});øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
inherits 类似于Java的extends关键字。uber方法类似于Java的super方法。它使得子类可以去调用父类中的方法。(方法名称必须和保留字、关键字区别开。) øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
我们可以这样使用: øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
myZParenizor = new ZParenizor(0); øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
myString = myZParenizor.toString(); øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
这时,myString返回的是"-0-"。 øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
JavaScript没有类,但是我们能去模拟它。øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
多重继承 øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
通过控制一个函数的prototype对象,我们能实现多重继承,使一个类继承自多个类。滥用多重继承实现起来会非常困难而且可能会造成名称冲突。我们可以用JavaScript实现杂乱的多重继承。但是在下面的例子中我将会使用一种更规范的方式——Swiss Inheritance。 øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
假如有一个NumberValue类,它有一个setValue方法来验证值是否在一个确定的区间内,可能的话还会抛出一个异常。在ZParenizor类里我只需要setValue方法和setRange方法。我不需要toString方法。因此,我可以这样写: øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
ZParenizor.swiss(NumberValue, 'setValue', 'setRange'); øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
这样就只向类中添加了需要的方法øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
寄生继承øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
有另外一个方法去重写ZParenizor,而不继承自Parenizor类我们在这个类里面写一个构造函数去调用Parenizor的构造函数然后构造函数中添加私有的方法而不是有的。代码如下: øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
function ZParenizor2(value) øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
{øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
    var that = new Parenizor(value); øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
    that.toString = function () øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
  { øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
        if (this.getValue()) øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
        { øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
            return this.uber('toString'); øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
        }øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
        return "-0-" øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
    };øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
    return that;øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
}øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
传统的继承是is-a关系,寄生的继承是was-a-but-now's-a关系。构造函数在构造类的时候起到了非常重要的作用。 øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
类扩展 øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
JavaScript的特性使我们可以添加或替换一个已经存在的类的方法。我们可以在任何时候调用method方法,新添加或替换的方法将会出现在类所有的实例中。我们可以在任何时候去扩展一个类。继承会追本溯源。øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
对象扩展 øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
在面向对象的语言里,如果你需要一个类,而这个类与另一个只有细微的差别。你就不得不去定义一个新的类。在JavaScript里,你可以向一个类里面添加方法而不是去添加新的类。这有非常大的好处,因为你可以少写很多类并且类将变得非常简单。在JavaScript里对象就像是一个哈希表(hashtable),你可以随时随地地向里面添加值。如果值是一个函数,则他会变成一个方法。 øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
因此对于上面的例子,我完全不需要再去定义一个ZParenizor类。我只需要去稍微的修改一个我的实例。øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
myParenizor = new Parenizor(0); øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
myParenizor.toString = function () øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
{    øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
    if (this.getValue()) øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
    {øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
        return this.uber('toString'); øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
    } øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
    return "-0-"; øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
}; øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
myString = myParenizor.toString();øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
我向myParenizor实例里面添加了一个toString方法而不用去使用任何形式的继承。我们可以扩展单独的类实例。øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
小玩意儿 øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
为了使上面的例子能够运行,我写了四个小方法øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
method方法。method方法用于向一个类添加方法。øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
Function.prototype.method = function (name, func) øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
{øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
    this.prototype[name] = func; øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
    return this; øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
}; øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
这样就向Function对象的原型里面添加了一个公共的方法,然后所有的函数都会通过类扩展得到这个方法。需要一个函数名和一个函数,然后将这个函数添加到对象的原型里面。返回的是this。当我写一个函数但不想让它放回一个值,通常地,我就会让它返回一个this。 øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
下一个就是inherits方法,它表明一个类继承自另外一个。他可以在类被定义了,但类的方法还没有添加去继承这个类。 øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
Function.method('inherits', function (parent) øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
{ øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
    var d = {}, p = (this.prototype = new parent()); øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
    this.method('uber', function uber(name) øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
    { øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
        if (!(name in d)) øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
        { øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
            d[name] = 0; øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
        }øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
        var f, r, t = d[name], v = parent.prototype; øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
        if (t) øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
        {øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
            while (t)øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
        {øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
        v = v.constructor.prototype; øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
        t -= 1;øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
    }øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
    f = v[name]; øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
}øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
elseøÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
{    øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
    f = p[name]; øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
    if (f == this[name]) øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
    {øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
        f = v[name]; øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
    }øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
} øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
d[name] += 1; øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
r = f.apply(this, Array.prototype.slice.apply(arguments, [1])); øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
d[name] -= 1; øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
return r; øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
}); øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
return this; øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
});øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
再一次,我扩展了函数这个对象。我实例化了一个父类,然后用它里面的方法作为一个新的原型。我同样也修改了它的构造函数并向它的原型里面添加了uber函数。 øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
uber方法看起来就像是自己原型里面的方法一样。这个函数援引了寄生继承或对象扩展。如果我们采用传统的继承,我们就不得不在父类的原型中去寻找函数。return语句用了函数的apply方法去调用函数,通过设置一个函数的参数数组。参数都从这个参数数组里面获取。øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
最后就是swiss方法。 øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
Function.method('swiss', function (parent) øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
{ øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
    for (var i = 1; i < arguments.length; i += 1) øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
    {øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
        var name = arguments; øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
        this.prototype[name] = parent.prototype[name]; øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
    } øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
    return this; øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
});øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
swiss方法循环了参数数组。它从父类的原型中拷贝了每一个所需的函数到新类的原型中。øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
小节øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
JavaScript可以像传统面向对象的语言那样来用,但它有其独有的表达方式。纵观传统继承,Swiss继承,寄生继承,类扩展和对象扩展,这些大量的代码重用方式都是来自于一个被认为是Java缩略版、雕虫小技的JavaScript语言实现的。 øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
正因为JavaScript里的对象是如此的灵活,你就去想不同的类结构。复杂的结构是不合适的;简单的结构就能胜任和具有丰富的表达力。 øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
后记 øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
这篇文章是一篇翻译的文章。昨天发了一篇名为"JavaScript基础之继承(附实例)"的帖子,也是说JavaScript继承的。但写那篇帖子的时候没有看见这篇文章,不然就能将两篇帖子合二为一了。这两篇文章从不同的角度说明了继承的实现,结合起来看比较好。 øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø
JavaScript基础写到这里共三篇,就不再写了。一是看到标准里面有那么多好的规范但是各浏览器却在很多方面"开小灶",看着有些难受。二是女朋友现在在外面实习学习asp.net,晚上回去得给她讲asp.net也没有时间研究JavaScript了。只能暂时放放了。
øÕc:Êg°ðÍwww.netcsharp.cn¥3³á¢íÔø