`
javamore
  • 浏览: 63927 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

大师Crockford的文章Classical Inheritance in JavaScript (ThoughtScript翻译)

阅读更多

大师Douglas Crockford 的文章,现有的中文翻译实在不该恭维,故冲动的翻译了下。

转载请注明出于:http://thoughtscript.appspot.com/Classical-Inheritance-in-JavaScript

 

 

Classical Inheritance in JavaScript 

Douglas Crockford    www.crockford.com   翻译:ThoughtScript  原文链接 http://javascript.crockford.com/inheritance.html

And you think you're so clever and classless and free  — John Lennon

JavaScript 一种是与类无关的面向对象语言(基于对象),因此Javascript使用了基于原型的方式(原型链)代替传统面向对象语言的继承方式。这可能会让过去习惯于使用C++、Java这类面向对象语言继承方式的程序员感到困惑。但马上你就能认识到实际上Javascript的原型方式确实比传统的方式更能表达继承关系。

JavaJavaScript
Strongly-typed Loosely-typed
Static Dynamic
Classical Prototypal
Classes Functions
Constructors Functions
Methods Functions

但是,首先我们需要思考我们使用继承的目的是什么?有两个主要的方面。第一是出于类型转换。我们希望语言环境能自动的对引用类的进行类型转换,但有时是无法通过类型系统的安全检查的,所以通常我们需要会对引用的对象进行显示转换,这对强类型语言至关重要,但是对像JavaScript这样弱类型语言却是无关紧要,因为Javascript中对象的引用不需要类型转换。
第二是考虑代码重用。通常我们通过使一系列不同对象实现相同的接口来达到重用。类继承方式通过创建一个单独的定义集合接口让重用变的可能,通常我们看到一些对象非常相似,但是对象间也存在区别,主要是增加或修改了一小部分方法。传统的类继承方式对代码重用的确有用,但JavaScript的原型方式更有用。
为了证明这点,我们将会介绍一些 
sugar 让我们的代码形式类似于传统语言的形式。并且我们将展示在传统语言中不可行但却非常有用的模式,最后,我们将解释这些 sugar

Classical Inheritance 类继承方式

首先,我们定义一个Parenizor类,Parenizor拥有设置和获取value变量的方法,和一个返回把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方法但返回“-0-”如果值为零或空。

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没有类的概念,但是我们依旧可以按类方式的编程。


Multiple Inheritance 多重继承

过操纵一个函数的原型对象,我们可以实现多重继承,使我们能创建一个继承多个类的不同方法。
复杂的多重继承难以实现,并有可能遭受潜在的方法名称冲突。我们可以在JavaScript中实现复杂的多重继承,但对于这个例子中,我们将使用一种称为Swiss Inheritance的更严格形式。
假设有一个NumberValue的类,具有 一个setValue 方法用来检查value变量是否是指定范围内的数字,并在需要的时候可以抛出异常。我们只需要ZParenizor的 setValue和setRange方法,而不需要toString方法,所以我们可以这样写:
ZParenizor.swiss(NumberValue, 'setValue', 'setRange');
这里只把我们请求的方法添加到了ZParenizor类中。


Parasitic Inheritance 寄生继承

里用另一种代替继承自Parenizor的方法来定义ZParenizor。寄生继承并不直接从 Parenizor继承,而是通过在ZParenizor构造器内部中调用Parenizor构造器,通过对Parenizor构造器返回的结果that进行修改,最后返回修改后的that。而这个构造器添加的是特权方法(privileged methods)而非公共方法。

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(原来是……而现在是……) 的关系。在对象构造中构造器扮演了主要的角色。注意uber方法(代替super保留字)对特权方法仍然有效。


Class Augmentation 类扩展

 
JavaScript的动态特性使我们能够添加或替换现有类中的方法。我们能在任何时候调用method方法,不管现有的还是未来创建的类实例都将会含有新添加的方法,这样我们在随时都能很直接的扩展类的方法。而继承是通过追溯的方式工作的,和类扩展是不同的。我们把这种方式命名为“Class Augmentation ”是因为想避免Java中的extends(扩展)混淆,这是两个完全不同的概念。


Object Augmentation 对象扩展

 
在静态的面向对象语言中,假如你需要一个只是与另一已经存在对象稍有不同的对象,你必须重新定义一个新的类。但在Javascript,你可以添加单个对象的方法,而不需要定义额外的类。这样你可以写更少更简单的类,这是Javascript非常强大的特性。把每一个Javascript对象现象成一张张哈希表,你可以随时添加新的变量到哈希表中,如何变量是函数,变量就会成为对象的方法。
这样在上面的例子中,我完全不需要用到ZParenizor类,就能以很简单的方式修改实例对象来达到扩展对象。

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


这里我们没有使用任何形式的继承就增加了 toString方法到myParenizor 实例对象中。这是因为JavaScript是与类无关的语言,所以你可以扩展每一个独立的实例对象。


Sugar


了让上面的例子能运行,我写了四个sugar 方法。第一个是method方法,用来把实例方法添加到类中。


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


这里添加了一个公共的方法到Function.prototype,这里通过类扩展的方式这样所有的方法都可以使用method方法。这你需要一个函数名称和函数,作为参数,将它们添加到一个函数的原型对象中。
这里返回本身(this)。当我写一个方法,它不需要返回值时,我通常会返回它本身,这样可以允许写成链式语句的编程风格。
下一个是inherits方法,用来让一个类继承另一个类。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。在添加uber方法到prototype的同时修正constructor 字段。
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原型中拷贝成员到自身类的原型中。


Conclusion 结论

 
JavaScript可以传统语言方式那样使用,但它也有丰富独特的表达继承的方式。我们已经了解了 类继承方式,Swiss继承方式,寄生继承方式,类扩展和对象扩展。这一系列代码重用的模式来自被认为比Java更小,更简单的Javascript。
传统语言中的对象是固定的(hard),要在对象中添加新的成员变量的唯一方式就是创建新的类。但在Javascript中,对象是灵活(soft),添加一个新的成员到JavaScript对象中只需要作简单的赋值。
由于JavaScript中的对象是如此灵活,你必须对类层次结构有不同的认识。深层次(Deep hierarchies )是不适当的,浅层次(Shallow hierarchies )更具执行效率和可读性。
到现在我已经写了8年的JavaScript,期间我发现我从来没有使用过uber方法。调用父类成员(super)的想法在传统模式中是相当重要的,但是在原型和函数式的模式中这看起来并不是很有必要。从现在看来早期我支持传统模式中super的尝试是一个错误。

分享到:
评论

相关推荐

    JavaScript 继承详解(四)

    Classical Inheritance in JavaScript。 Crockford是JavaScript开发社区最知名的权威,是JSON、JSLint、JSMin和ADSafe之父,是《[removed] The Good Parts》的作者。 现在是Yahoo的资深JavaScript架构师,参与YUI的...

    Really JavaScript!——Douglas Crockford大师见面会报道.pdf

    ——Douglas Crockford大师见面会报道.pdf" 以下是从给定的文件中生成的相关知识点: 1. Douglas Crockford是谁?:Douglas Crockford是一位资深的JavaScript架构师,JavaScript程序员的必读书籍作者,也是互聯網...

    javascript.crockford.com.rar

    1. **文章和教程**:Crockford的网站可能包含了一系列关于JavaScript的文章和教程,解释了语言的各个方面,包括变量作用域、闭包、原型继承、函数表达式等。 2. **代码示例**:为了更好地理解理论,Crockford可能...

    JavaScript: The Good Parts

    Author Douglas Crockford, a member of JavaScript 2.0 committee at ECMA, is considered by many people in the development community to be the JavaScript expert., A beautiful, elegant, lightweight and ...

    node-crockford-inheritance:Crockford 对节点 js 的继承支持

    node-crockford-继承Crockford 对节点 js 的继承支持来自如何安装 npm install crockford-inheritance开始 var crockford = require('crockford-inheritance');

    讲解JavaScript的面向对象的编程

    本人一行注释一行代码翻译了该大师的艺术作品--目的说明它是在第1,2阶段文档演示的JavaScript面向对象的书写方式的进一步改进,它是现代JavaScript面向对象编程方式(使用基本类来编码)的过渡代码--没有它就没有当今...

    JavaScript语言精粹完整版

    在书中,Crockford将自己多年的实践经验与对JavaScript深刻理解相结合,为读者呈现了一个清晰、严谨且实用的JavaScript学习路径。 ### JavaScript的核心概念 #### 变量与数据类型 JavaScript是一种弱类型的语言,...

    [JavaScript语言精粹].(JavaScript.The.Good.Parts).Douglas.Crockford.文字版

    [JavaScript语言精粹].(JavaScript.The.Good.Parts).Douglas.Crockford.文字版

    JavaScript语言精粹(JavaScript.The.Good.Parts)

    Douglas Crockford 在本书中剥开了JavaScript 沾污的外衣,抽离出一个具有更好可靠性、可读性和可维护性的JavaScript 子集,让你看到一门优雅的、轻量级的和非常富有表现力的语言。作者从语法、对象、函数、继承、...

    JavaScript(ppk谈JavaScript+JavaScript语言精粹修订+Secrets of the JavaScript Ninja)

    “JavaScript语言精粹(修订版)”可能是Douglas Crockford的著作,他是一位JavaScript的先驱者,对语言的某些部分进行了深入探讨,特别是关于代码风格、最佳实践和设计模式。这本书强调了JavaScript的精华部分,...

    JavaScript语言精粹.修订版 Javascript:The Good Parts 中英 pdf

    修订版",即《Javascript: The Good Parts》,是由知名的计算机科学家Douglas Crockford所著,这本书深入探讨了JavaScript的核心概念和最佳实践,为开发者提供了宝贵的洞察力。 书中的内容主要分为以下几个部分: ...

    javascript the good parts

    《JavaScript: The Good Parts》是JavaScript领域的经典之作,由知名编程专家Douglas Crockford撰写。这本书主要聚焦在JavaScript语言中那些优秀的特性上,旨在帮助开发者挖掘并利用这些特性来编写更加可靠、可读...

    JavaScript语言精粹(JavaScript.The.Good.Parts)中英文版集合

    Douglas Crockford在本书中剥开了javascript沾污的外衣,抽离出一个具有更好可靠性、可读性和可维护性的javascript子集,让你看到一门优稚的、轻量级的和非常富有表现力的语言。作者从语法、对象、函数、继承、数组...

    更好地使用JavaScript

    总之,根据Douglas Crockford的建议,我们可以得出如下知识点:学习和使用JavaScript的好的部分、避免JavaScript的坏部分,理解JavaScript在多种平台上的广泛适用性,以及掌握JavaScript数字类型的一些特殊行为。...

    JavaScript程序编码规范.pdf

    该规范由Douglas Crockford编写,并由cloudwater翻译成中文版。Crockford是一位著名的JavaScript专家,他提出的许多概念如JSON格式已经成为Web开发的标准之一。这份规范参考了Sun公司的Java编码规范,但由于...

    JavaScript语言精粹(修订版)_itjsveryclever_JavaScript语言精粹(修订版)_

    《JavaScript语言精粹(修订版)》是JavaScript编程领域的一部经典著作,由知名的JavaScript专家Douglas Crockford撰写。这本书深入浅出地探讨了JavaScript的核心特性,旨在帮助读者掌握这门语言的精髓,提高编程...

    Javascript必读

    JavaScript,作为一种广泛应用于互联网开发的脚本语言,已经成为前端工程师乃至整个程序员群体不可或缺的技能。JavaScript的威力不仅限于...不断学习和实践,将使你在编程世界中游刃有余,成为真正的JavaScript大师。

    JavaScript语言精粹(中+英文版)

    Douglas Crockford在本书中剥开了JavaScript沾污的外衣,抽离出一个具有更好可靠性、可读性和可维护性的JavaScript子集,让你看到一门优稚的、轻量级的和非常富有表现力的语言。作者从语法、对象、函数、继承、数组...

    JavaScript:The Good Parts May

    《JavaScript:The Good Parts May》是一本由Douglas Crockford撰写的经典著作,主要探讨了JavaScript编程语言中的一些精华部分。这本书对于理解和掌握JavaScript的核心概念、语法以及最佳实践提供了宝贵的指导,...

Global site tag (gtag.js) - Google Analytics