Javascript中的类式继承(Classical Inheritance)

译注:因为项目需要,最近学习了下Javscript。本来以为Javascript是个弱类型的脚本语言,大致看下就能搞定。到看到其面向对象部分时,发现与C++和Java的对象机制差别很大,有种耳目一新之感。遂找了些资料看,发现有个叫Douglas Crockford的大牛写得很深入,但都是英文的,便想把一些经典的翻译过来。此为其一。

Javascript 中的类式继承(Classical Inheritance)

英文原文

你聪明绝顶,又自由自在

——John Lennon


Javscript是种无
类型的面向对象语言,它采用一种与类式继承不同的原型式继承机制。虽然这让熟悉C++和Java等传统面向对象语言的程序员感到迷惑,但正如本文即将展示那样,Javascript的原型式继承方式比类式继承方式要强大得多。
但先有个问题——为什么我们关注继承方式呢?主要原因有二。其一是类型转化,强类型语言需要明确的机制以实现不同类型引用间的转化,但对Javascript这种弱类型语言来说,所有的对象引用都是同一类型,无需转化。其二是代码复用,可以定义一种类而建立大量具有相同方法的对象,也可以通过类的继承来创建许多具有相似方法的对象。类式继承方式在这方面做得很好,但原型式继承却能做到更好。
为说明这一点,我先引入几个基本函数,以便模仿传统类式语言的风格,然后给出一些类式语言中没有的模式,最后对这些基本函数作出解释。

类式继承

先来写个Parenizor类,它有个属性value及相应的set、get方法,和一个将value值包装在括号中的toString()方法。

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方法采用一个名字和函数对象作为参数,将其作为公共方法添加到类中。

故而可以写出

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

如你所料,myString的值正是“(0)”。

现在创建一个继承自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相当,允许在子类方法中调用父类的版本。(为避免与保留字冲突,特将名字改变了下。)

现在可以这样写

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

这次,myString的值是“-0-”了。

虽然Javascript没有类,但却可以写出“类式”代码。

多重继承

通过操作函数的prototype属性,可以实现多重继承,以建立继承多个类的方法的新类。任意的多重继承难于实现,并会造成命名冲突。虽然Javascript可以实现任意多重继承,但本例遵从一种称为Swiss继承的原则。

设存在NumberValue类,它有个setValue方法,检查属性value的值是否是某一范围内的数字,若不是则抛出异常。如果仅仅需要从这个类继承setValue和setRange给ZParenizor类话,只需这样写

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;
}
类式继承是“是一个”的关系,寄生继承是“以前是但现在不是”的关系。构造函数在对象的构造过程中作用重大。需要注意的是,uber方法在特权函数中仍然可用。

类增强

Javscript允许增加或替换任意一个已经存在的类的方法,并且可以在任何时候在该类现有的或将来的任意实例中调用该方法。任意刻都可以直接扩展一个类。但继承却只能在父类改变后,才可以在新继承的子类实例中调用改变后的方法。我们将这称为类增强,以避免与Java中具有另外含义的扩展(extends)相冲突。

对象增强

在静态面向对象语言中,即使需要一个与其它对象稍微不同的新对象,也要定义一个新类。但在Javascript中,可以为单个对象添加方法,而无需定义额外的类。这一点威力无穷,因为它可以减少类的数量和编写代码的难度。回忆下,Javascrip的t对象就像个哈希表,可以随时添加新属性,如果属性值是函数,则添加的就是新方法。

因此,上述代码其实并不需要ZParenizor类,可以仅仅通过修改实例来实现。

myParenizor = new Parenizor(0);
myParenizor.toString = function () {
    if (this.getValue()) {
        return this.uber('toString');
    }
    return "-0-";
};
myString = myParenizor.toString();
现在只是给myParenizor对象添加了个toString方法,而无需使用任何形式的继承。正是因为Javascript语言没有类的概念,我们才能演化任一单个对象实例。

基本函数

为使上述例子正常工作,我写了几个基本函数。首先是给类添加方法的method函数

Function.prototype.method = function (name, func) {
    this.prototype[name] = func;
    return this;
};
它给Function.prototype添加公共方法,以使所有函数都能通过类增强来获得该方法。它采用一个名字和一个函数作为参数,将其作为函数添加到函数的prototype对象中。

method的返回值为this。我在写没有返回值的函数时,习惯上返回一个this,这样便于链式编程。

下一个出场的函数是inherits,从字面便可看出,它用来使一个类继承自其它类。这个函数只能在两个类都定义后但引申类添加新方法之前使用。

Function.method('inherits', function (parent) {
    var d = {}, p = (this.prototype = new 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!我们创建了parent的新实例,并将其作为新的prototype,同时也修改了constructor属性,及为prototype添加uber方法。

uber方法在它自己的prototype中寻找给定名称的方法。它在寄生继承或对象增强的情况下使用。如果使用的是类式继承,则需要在parent的prototype中寻找给定名称的方法。return语句使用函数的apply方法以明确设置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属性,从parent的prototype复制一份给新类的prototype。

结论

虽然Javascript可以像类式继承那样使用,但它依旧有些非常独特的特性。我们阐述了类式继承、Swiss继承、寄生继承、类增强、对象增强。但这许多的复用模式却是来自一门比Java更小更简单的语言。

类式继承是很僵硬的,给“硬对象”添加新成员的唯一方式是创建一个新类。但Javascript中对象是“软”的,新成员可以简单地通过赋值来添加。

因为Javascript中的对象有如此强大的伸缩性,所以你会想对类式继承进行一些新的思考。深层次的继承结构通常并不合适,浅层次的继承才更有效、更有力。

我已经写了8年的Javascript程序,却从未发现需要用到uber函数之处。父类在类模式中固然非常重要,但在原型和函数模式中却可有可无。现在我认为在Javascript中模仿类模型是个错误。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值