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

  但首先,要搞清楚我们为什么如此关注继承?主要有两个原因。首先是方便类型的转换。我们希望语言系统能够对那些相似类的引用进行自动转换。而对于一个要求对引用对象进行显示转换的类型系统来说只能获得很少的类型安全性。这对于强类型语言来说很重要,但是在像JavaScript这样的松散型语言中,永远不需要对对象引用进行强制转换。

  第二个原因是代码的复用。代码中存在大量拥有相同方法的对象是十分常见的。类可以通过一组定义来创建它们。另外存在很多相似的对象也很普遍,这些对象中只有少数有关添加和修改的方法存在区别。类的继承可以很有效地解决这些问题,但原型继承更有效。

  为了说明这一点,我们将介绍一点语法糖,它允许我们以类似于传统的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)".

  现在我们创建另一个类继承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没有类,但是我们可以通过编程来实现它。

多重继承

  通过操作一个函数的原型对象,我们可以实现多重继承,从而使我们可以用多个类的方法来构建一个类。混合多重继承可能难以实现,并可能存在方法名称的冲突。我们可以在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

JavaScript中的类继承的更多相关文章

  1. 深入理解JavaScript中的类继承

    由于写本文时全部是在编辑器中边写代码边写感想的,所以,全部思想都写在代码注释里面了 // 类继承 //todo.1 extends 关键字 class Animal { constructor(nam ...

  2. 理解JavaScript中的原型继承(2)

    两年前在我学习JavaScript的时候我就写过两篇关于原型继承的博客: 理解JavaScript中原型继承 JavaScript中的原型继承 这两篇博客讲的都是原型的使用,其中一篇还有我学习时的错误 ...

  3. JavaScript中的类

          JavaScript类的相关知识 1.例子 /* 例1 */// 定义一个构造函数function Range(from, to){ this.from = from; this.to = ...

  4. JavaScript中一个对象如何继承另外一个对象

    如题,JavaScript中一个对象a如何继承另外一个对象b.即将b中的属性和方法复制到a中去. 面试中遇到了这个问题,当时脑子里的想法是: 1.除了循环遍历复制,还能怎样 2.javascript中 ...

  5. Javascript中的类实现

    Javascript本身并不支持面向对象,它没有访问控制符,它没有定义类的关键字class,它没有支持继承的extend或冒号,它也没有用来支持虚函数的virtual,不过,Javascript是一门 ...

  6. JavaScript中定义类的方式详解

    本文实例讲述了JavaScript中定义类的方式.分享给大家供大家参考,具体如下: Javascript本身并不支持面向对象,它没有访问控制符,它没有定义类的关键字class,它没有支持继承的exte ...

  7. JavaScript中创建类,赋值给ajax中的data参数

    缘由:因为要给根据是否选中checkbox来动态增加ajax中data的属性(ajax的data属性格式的几种方法,参考http://www.jb51.net/article/46676.htm) d ...

  8. javascript中的原型继承

    在Javascript面向对象编程中,原型继承不仅是一个重点也是一个不容易掌握的点.在本文中,我们将对Javascript中的原型继承进行一些探索. 基本形式 我们先来看下面一段代码: <cod ...

  9. C++中的类继承之单继承&多继承&菱形继承

     C++中的类继承之单继承&多继承&菱形继承 单继承是一般的单一继承,一个子类只 有一个直接父类时称这个继承关系为单继承.这种关系比较简单是一对一的关系: 多继承是指 一个子类有两个或 ...

随机推荐

  1. java_web学习(七) MySQL的配置与安装

    一:下载MySQL 1.进入oracle官网 2. 3. 4. 5. 6. 7. 网址:https://dev.mysql.com/downloads/file/?id=469273 二:配置MySQ ...

  2. echarts图表属性设置

    原地址:http://blog.csdn.net/she_lover/article/details/51448967theme = { // 全图默认背景 // backgroundColor: ‘ ...

  3. sqlalchemy-数据库操作

    import datetime from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarat ...

  4. deeplearning.ai 卷积神经网络 Week 3 目标检测 听课笔记

    本周的主题是对象检测(object detection):不但需要检测出物体(image classification),还要能定位出在图片的具体位置(classification with loca ...

  5. LibreOJ NOI Round #1 Day 1 B. 失控的未来交通工具

    瞬间移动 官方题解 题意:一个带边权无向图,加边以及询问在 x,x+b,...,x+(c−1)bx,x+b,...,x+(c-1)bx,x+b,...,x+(c−1)b 这些数中,有多少存在一条与之模 ...

  6. bzoj:3730: 震波

    Description 在一片土地上有N个城市,通过N-1条无向边互相连接,形成一棵树的结构,相邻两个城市的距离为1,其中第i个城市的价值为value[i].不幸的是,这片土地常常发生地震,并且随着时 ...

  7. 微积分入门("SX"T版)

    哎,微积分,表示暂时并没有很深入的研究……虽然高中有教,但是好像跟小西瓜学的顺序不太一样,嗯……教微积分之前不应该把极限学下来么……不管了,本文按傻X腾的理解来搞吧. 极限……大学的东西喔,我们先来认 ...

  8. opencv+vs配环境

    首先,一定要注意debug和release下配的项目设置是有区分的!!!!!!!!!!! 1.注意自己的电脑是64位还是32位 2.要在环境变量中设置环境变量,环境变量从前向后扫描,用64位环境变量时 ...

  9. linux下如何删除文件夹?

    直接rm就可以了,不过要加两个参数-rf 即:rm -rf 目录名字-r 就是向下递归,不管有多少级目录,一并删除:-f 就是直接强行删除,不作任何提示的意思. 例如:删除文件夹实例: rm -rf ...

  10. 应用中Token的作用

    Token 的作用 Token,就是令牌,最大的特点就是随机性,不可预测.一般黑客或软件无法猜测出来. 那么,Token有什么作用?又是什么原理呢? Token一般用在两个地方: 1)防止表单重复提交 ...