JavaScript中的类继承

  JavaScript是一个无class的面向对象语言,它使用原型继承而非类继承。这会让那些使用传统面向对象语言如C++和Java的程序员们感到困惑。正如咱们所看到的,JavaScript的原型继承比类继承具备更强的表现力。javascript

  但首先,要搞清楚咱们为何如此关注继承?主要有两个缘由。首先是方便类型的转换。咱们但愿语言系统可以对那些类似类的引用进行自动转换。而对于一个要求对引用对象进行显示转换的类型系统来讲只能得到不多的类型安全性。这对于强类型语言来讲很重要,可是在像JavaScript这样的松散型语言中,永远不须要对对象引用进行强制转换。html

  第二个缘由是代码的复用。代码中存在大量拥有相同方法的对象是十分常见的。类能够经过一组定义来建立它们。另外存在不少类似的对象也很广泛,这些对象中只有少数有关添加和修改的方法存在区别。类的继承能够颇有效地解决这些问题,但原型继承更有效。java

  为了说明这一点,咱们将介绍一点语法糖,它容许咱们以相似于传统的class的语言来编写代码。而后咱们将介绍一些有用的模式,这些模式不适用于传统的class语言。最后,咱们将对语法糖进行解释。程序员

类继承

  首先,咱们添加了一个Parenizor类,包含set和get两个方法,分别用来设置和获取value,以及一个toString方法,用来对parens中的value进行包装。编程

function Parenizor(value) {
    this.setValue(value);
}

Parenizor.method('setValue', function (value) {
    this.value = value;
    return this;
});

Parenizor.method('getValue', function () {
    return this.value;
});

Parenizor.method('toString', function () {
    return '(' + this.getValue() + ')';
});

  语法看起来有点不太同样,可是应该很好懂。方法method接受方法的名称和一个function,并将这个function做为公共方法添加到类中。数组

  而后咱们能够这样写:安全

myParenizor = new Parenizor(0);
myString = myParenizor.toString();

  正如你所指望的,myString的值为"(0)".app

  如今咱们建立另外一个类继承Parenizor,除了toString方法中对于value为空或0的状况会输出"-0-"外其他都和Parenizor相同。函数

function ZParenizor(value) {
    this.setValue(value);
}

ZParenizor.inherits(Parenizor);

ZParenizor.method('toString', function () {
    if (this.getValue()) {
        return this.uber('toString');
    }
    return "-0-";
});

  这里的inherits方法与Java中的extends方法相似,uber方法也与Java中的super方法相似。它容许一个方法调用父类中的方法(只是改了名称以避开保留字的限制)。this

  而后咱们能够这样写:

myZParenizor = new ZParenizor(0);
myString = myZParenizor.toString();

  这一次,myString的值为"-0-".

  JavaScript没有类,可是咱们能够经过编程来实现它。

多重继承

  经过操做一个函数的原型对象,咱们能够实现多重继承,从而使咱们能够用多个类的方法来构建一个类。混合多重继承可能难以实现,并可能存在方法名称的冲突。咱们能够在JavaScript中实现混合多重继承,可是在本例中咱们将使用一个更严格的被称之为Swiss继承的形式。

  假设有一个NumberValue类,包含一个方法setValue,该方法检查value是否为某个特定范围内的数字,必要的时候会抛出异常。咱们只须要ZParenizorsetValuesetRange方法,而不须要toString方法。那么咱们能够这样写:

ZParenizor.swiss(NumberValue, 'setValue', 'setRange');

  这样只会将咱们须要的方法添加到类中。

寄生继承

  ZParenizor还有另一种写法。除了从Parenizor类继承,咱们还能够在构造函数中调用Parenizor的构造函数,并传递返回的结果。经过这种方式,咱们给构造函数添加特权方法,而不用再去为其添加公共方法。

function ZParenizor2(value) {
    var that = new Parenizor(value);
    that.toString = function () {
        if (this.getValue()) {
            return this.uber('toString');
        }
        return "-0-"
    };
    return that;
}

  类的继承是is-a关系(公有继承),而寄生继承是was-a-but-now's-a关系(私有继承与公有继承)。构造函数在对象的构造中发挥了很大的做用。注意ubersuper方法仍然可用于特权方法。

类的扩充

  JavaScript的动态性容许咱们添加或替换现有类的方法,method方法能够随时被调用,这样类的全部实例在如今和未来都会有这个方法。咱们能够在任什么时候候对一个类进行扩展。继承具备追溯性,咱们把这个叫作类的扩充(Class Augmentation),以免与Java的extends产生混淆。

对象的扩充

  在静态面向对象语言中,若是你想要一个对象与另外一个对象略微不一样,就须要定义一个新的类。在JavaScript中,你能够将方法添加到单个的对象中,而不须要在定义额外的类。这个很是强大,由于你只须要写不多的类,而且类均可以很简单。回想一下,JavaScript对象就像哈希表,你能够随时添加新的值,若是值是function,那么它就成了一个方法。

  所以在上面的示例中,我根本不须要ZParenizor类。我能够简单地修改个人实例。

myParenizor = new Parenizor(0);
myParenizor.toString = function () {
    if (this.getValue()) {
        return this.uber('toString');
    }
    return "-0-";
};
myString = myParenizor.toString();

  我将toString方法添加到个人myParenizor实例中,而没有使用任何形式的继承。咱们能够修改单个的实例,由于语言是无class的。

Sugar(语法糖)

  为了使上面的示例能正常工做,我写了四个sugar方法。首先是method方法,它将一个实例方法添加到类中。

Function.prototype.method = function (name, func) {
    this.prototype[name] = func;
    return this;
};

  它在Function.prototype上添加了一个公共方法,所以全部的函数都经过Class Augmentation(类的扩充)得到了该方法。它接受一个名称和一个函数,并将它们添加到函数的原型对象中。

  它返回this. 当我编写一个不须要返回值的方法时,我一般都会返回this,这样就具备了一个级联式的编程风格。

  接下来是inherits方法,它用来表示一个类从另外一个类继承。应该在两个类都被定义以后再调用这个方法,而且在继承类的方法以前添加该方法。

Function.method('inherits', function (parent) {
    this.prototype = new parent();
    var d = {}, 
        p = this.prototype;
    this.prototype.constructor = parent; 
    this.method('uber', function uber(name) {
        if (!(name in d)) {
            d[name] = 0;
        }        
        var f, r, t = d[name], v = parent.prototype;
        if (t) {
            while (t) {
                v = v.constructor.prototype;
                t -= 1;
            }
            f = v[name];
        } else {
            f = p[name];
            if (f == this[name]) {
                f = v[name];
            }
        }
        d[name] += 1;
        r = f.apply(this, Array.prototype.slice.apply(arguments, [1]));
        d[name] -= 1;
        return r;
    });
    return this;
});

  咱们继续对Function进行扩充。咱们建立了一个父类的实例,并将其做为新的原型。咱们还修改了构造函数的字段,并将uber方法添加到原型中。

  Uber方法在本身的原型中查找指定的方法。这是在寄生继承或对象扩充的状况下调用的函数。若是咱们进行类的继承,那么咱们就须要在父类的原型中找到这个函数。Return语句使用函数的apply方法来调用function,显示地设置this并传递一个数组参数。参数(若是有的话)从arguments数组中获取。惋惜arguments数组不是一个真正的数组,因此咱们不得再也不次使用apply来调用的slice方法。

  最后,是swiss方法。

Function.method('swiss', function (parent) {
    for (var i = 1; i < arguments.length; i += 1) {
        var name = arguments[i];
        this.prototype[name] = parent.prototype[name];
    }
    return this;
});

  Swiss方法对arguments进行遍历。对每个name,它都从父类的原型中复制一个成员到新类的原型中。

结论

  JavaScript能够像class语言同样来使用,但它也具备至关独特的表现力。咱们研究了类的继承,Swiss继承,寄生继承,类的扩充以及对象的扩充。这种大量代码的复用模式来自于一种被认为比Java更小,更简单的语言。

  类的对象很是严格,要将一个新成员添加到对象中,惟一的方法就是建立一个新类。而在JavaScript中,对象是松散的,能够经过简单的赋值操做将一个新成员添加到对象中。

  因为JavaScript中的对象很是灵活,因此你须要对类的层次结构进行不一样的考虑。深层次的结构并不太适用,相反,浅层次的结构更高效,更具备表现力。

 

我从事编写JavaScript代码已经有14年了,并且我历来没有发现须要使用uber函数。Super在class模式中十分重要,可是在原型和函数式模式中不是必须的。如今看来我早期尝试在JavaScript中支持class模式是一个错误。

 

原文地址:Classical Inheritance in JavaScript

相关连接:http://www.cnblogs.com/sanshi/archive/2009/07/08/1519036.html

相关文章
相关标签/搜索