热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

JavaScript中的类继承_javascript技巧

JavaScript一种没有类的,面向对象的语言,它使用原型继承来代替类继承。
Javascript Inheritance

DouglasCrockford
www.crockford.com

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

Javascript一种没有类的,面向对象的语言,它使用原型继承来代替类继承。这个可能对受过传统的面向对象语言(如C++和Java)训练的程序员来说有点迷惑。Javascript的原型继承比类继承有更强大的表现力,现在就让我们来看看。

Java

Javascript

强类型

弱类型

静态

动态

基于类

基于原型

函数

构造器

函数

方法

函数

但首先,为什么我们如此关心继承呢?主要有两个原因。第一个是类型有利。我们希望语言系统可以自动进行类似类型引用的转换cast。小类型安全可以从一个要求程序显示地转换对象引用的类型系统中获得。这是强类型语言最关键的要点,但是这对像Javascript这样的弱类型语言是无关的,Javascript中的类引用无须强制转换。

第二个原因是为了代码的复用。在程序中常常会发现很多对象都会实现同一些方法。类让建立单一的一个定义集中建立对象成为可能。在对象中包含其他对象也包含的对象也是很常见的,但是区别仅仅是一小部分方法的添加或者修改。类继承对这个十分有用,但原型继承甚至更有用。

要展示这一点,我们要介绍一个小小的“甜点”可以主我们像一个常规的类语言一样写代码。我们然后会展示一些在类语言中没有的有用的模式。最后,我们会就会解释这些“甜点”。
类继承
首先,我们建立一个Parenizor类,它有成员 value的get和set方法,还有一个会将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-"如果value是零或者空。

代码如下:


function ZParenizor(value) {
this.setValue(value);
}
ZParenizor.inherits(Parenizor);
ZParenizor.method("e;toString"e;, 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中实现混合多继承,但这个例子我们将使用一个较规范的形式称为瑞士继承SwissI nheritance.
假设有一个NumberValue类有一个setValue方法用来检查 value是不是在一个指定范围内的一个数,并在适当的时候抛出异常。我们只要它的setValue和 setRange方法给我们的ZParenizor。我们当然不想要它的toString方法。这样,我们写到:

代码如下:


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


这个将仅仅添加需要的方法。
寄生继承
这是另一个书写 ZParenizor类的方法。并不从 Parenizor继承,而是写了一个调用了Parenizor构造器的构造器,并对结果修改最后返回这个结果。这个构造器添加的是特权方法而非公共方法。

代码如下:


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


类继承是一种“是……”的关系,而寄生继承是一个关于“原是……而现在是……”的关系。构造器在对象的构造中扮演了大量的角色。注意uber (代替super关键字)对特权方法仍有效。
类扩展
Javascript的动态性让我们可以对一个已有的类添加或替换方法。我们可以在任何时候调用方法。我们可以随时地扩展一个类。继承不是这个方式。所以我们把这种情况称为“类扩展”来避免和Java的extends──也叫扩展,但不是一回事──相混淆。
对象扩展
在静态面向对象语言中,如果你想要一个对象和另一个对象有所区别,你必须新建立一个类。但在Javascript中,你可以向单独的对象添加方法而不用新建类。这会有巨大的能量因为你就可以书写尽量少的类,类也可以写得更简单。想想Javascript的对象就像哈希表一样。你可以在任何时候添加新的值。如果这个值是一个函数,那他就会成为一个方法。
这样在上面的例子中,我完全不需要 ZParenizor类。我只要简单修改一下我的实例就行了。

代码如下:


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


我们给 myParenizor实例添加了一个 toString方法而没有使用任何继承。我们可以演化单独的实例因为这个语言是无类型的。
小甜点
要让上面的例子运行起来,我写了四个“甜点”方法。首先,method方法,可以把一个实例方法添加到一个类中。

代码如下:


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


这个将会添加一个公共方法到 Function.prototype中,这样通过类扩展所有的函数都可以用它了。它要一个名称和一个函数作为参数。
它返回 this。当我写一个没有返回值的方法时,我通常都会让它返回this。这样可以形成链式语句。
下面是 inherits方法,它会指出一个类是继承自另一个类的。它必须在两个类都定义完了之后才能定义,但要在方法继承之前调用。

代码如下:


Function.method('inherits', function (parent) {
var d = 0, p = (this.prototype = new parent());
this.method('uber', function uber(name) {
var f, r, t = d, 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 += 1;
r = f.apply(this, Array.prototype.slice.apply(arguments, [1]));
d -= 1;
return r;
});
return this;
});


再来,我们扩展 Function类。我们加入一个 parent类的实例并将它做为新的prototype。我们也必须修正constructor字段,同时我们加入uber方法。
uber方法将会在自己的prototype中查找某个方法。这个是寄生继承或类扩展的一种情况。如果我们是类继承,那么我们要找到parent的prototype中的函数。return语句调用了函数的apply方法来调用该函数,同时显示地设置this并传递参数。参数(如果有的话)可以从arguments数组中获得。不幸的是,arguments数组并不是一个真正的数组,所以我们又要用到apply来调用数组中的slice方法。
最后,swiss方法

代码如下:


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


The swiss方法对每个参数进行循环。每个名称,它都将parent的原型中的成员复制下来到新的类的prototype中。
总结
Javascript可以像类语言那样使用,但它也有一种十分独特的表现层次。我们已经看过了类继承、瑞士继承、寄生继承、类扩展和对象扩展。这一等系列代码复用的模式都能来自这个一直被认为是很小、很简单的Javascript语言。
类对象属于“硬的”。给一个“硬的”对象添加成员的唯一的方法是建立一个新的类。在Javascript中,对象是“软的”。要给一个“软”对象添加成员只要简单的赋值就行了。
因为Javascript中的类是这样地灵活,你可能会还想到更复杂的类继承。但深度继承并不合适。浅继承则较有效而且更易表达。
推荐阅读
  • 本文详细介绍了云服务器API接口的概念和作用,以及如何使用API接口管理云上资源和开发应用程序。通过创建实例API、调整实例配置API、关闭实例API和退还实例API等功能,可以实现云服务器的创建、配置修改和销毁等操作。对于想要学习云服务器API接口的人来说,本文提供了详细的入门指南和使用方法。如果想进一步了解相关知识或阅读更多相关文章,请关注编程笔记行业资讯频道。 ... [详细]
  • Android中高级面试必知必会,积累总结
    本文介绍了Android中高级面试的必知必会内容,并总结了相关经验。文章指出,如今的Android市场对开发人员的要求更高,需要更专业的人才。同时,文章还给出了针对Android岗位的职责和要求,并提供了简历突出的建议。 ... [详细]
  • 本文介绍了2019年上半年内蒙古计算机软考考试的报名通知和考试时间。考试报名时间为3月1日至3月23日,考试时间为2019年5月25日。考试分为高级、中级和初级三个级别,涵盖了多个专业资格。报名采取网上报名和网上缴费的方式进行,报考人员可登录内蒙古人事考试信息网进行报名。详细内容请点击查看。 ... [详细]
  • 如何提高PHP编程技能及推荐高级教程
    本文介绍了如何提高PHP编程技能的方法,推荐了一些高级教程。学习任何一种编程语言都需要长期的坚持和不懈的努力,本文提醒读者要有足够的耐心和时间投入。通过实践操作学习,可以更好地理解和掌握PHP语言的特异性,特别是单引号和双引号的用法。同时,本文也指出了只走马观花看整体而不深入学习的学习方式无法真正掌握这门语言,建议读者要从整体来考虑局部,培养大局观。最后,本文提醒读者完成一个像模像样的网站需要付出更多的努力和实践。 ... [详细]
  • 从高级程序员到CTO的4次能力跃迁!如何选择适合的技术负责人?
    本文讲解了从高级程序员到CTO的4次能力跃迁,以及如何选择适合的技术负责人。在初创期、发展期、成熟期的每个阶段,创业公司需要不同级别的技术负责人来实现复杂功能、解决技术难题、提高交付效率和质量。高级程序员的职责是实现复杂功能、编写核心代码、处理线上bug、解决技术难题。而技术经理则需要提高交付效率和质量。 ... [详细]
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • Centos7.6安装Gitlab教程及注意事项
    本文介绍了在Centos7.6系统下安装Gitlab的详细教程,并提供了一些注意事项。教程包括查看系统版本、安装必要的软件包、配置防火墙等步骤。同时,还强调了使用阿里云服务器时的特殊配置需求,以及建议至少4GB的可用RAM来运行GitLab。 ... [详细]
  • 本文介绍了新款奇骏的两个让人上瘾的功能,分别是智能互联系统和BOSE音响。通过对新款奇骏的配置和功能进行评测,探讨了这两个新增功能的使用体验和优势。此外,还介绍了新款奇骏的其他配置和改进,如增加的座椅和驾驶辅助系统,以及内饰的舒适性提升。对于喜欢音响的消费者来说,BOSE音响的升级也是一个亮点。最后,文章提到了BOSE音响的数字还原能力,以及7座版无法配备BOSE音响的原因。 ... [详细]
  • 本文介绍了adg架构设置在企业数据治理中的应用。随着信息技术的发展,企业IT系统的快速发展使得数据成为企业业务增长的新动力,但同时也带来了数据冗余、数据难发现、效率低下、资源消耗等问题。本文讨论了企业面临的几类尖锐问题,并提出了解决方案,包括确保库表结构与系统测试版本一致、避免数据冗余、快速定位问题等。此外,本文还探讨了adg架构在大版本升级、上云服务和微服务治理方面的应用。通过本文的介绍,读者可以了解到adg架构设置的重要性及其在企业数据治理中的应用。 ... [详细]
  • 禁止程序接收鼠标事件的工具_VNC Viewer for Mac(远程桌面工具)免费版
    VNCViewerforMac是一款运行在Mac平台上的远程桌面工具,vncviewermac版可以帮助您使用Mac的键盘和鼠标来控制远程计算机,操作简 ... [详细]
  • 本文介绍了在Mac上安装Xamarin并使用Windows上的VS开发iOS app的方法,包括所需的安装环境和软件,以及使用Xamarin.iOS进行开发的步骤。通过这种方法,即使没有Mac或者安装苹果系统,程序员们也能轻松开发iOS app。 ... [详细]
  • 如何在跨函数中使用内存?
    本文介绍了在跨函数中使用内存的方法,包括使用指针变量、动态分配内存和静态分配内存的区别。通过示例代码说明了如何正确地在不同函数中使用内存,并提醒程序员在使用动态分配内存时要手动释放内存,以防止内存泄漏。 ... [详细]
  • 本文介绍了自动化测试专家Elfriede Dustin在2008年的文章中讨论了自动化测试项目失败的原因。同时,引用了IDT在2007年进行的一次软件自动化测试的研究调查结果,调查显示很多公司认为自动化测试很有用,但很少有公司成功实施。调查结果表明,缺乏资源是导致自动化测试失败的主要原因,其中37%的人认为缺乏时间。 ... [详细]
  • 2022年的风口:你看不起的行业,真的很挣钱!
    本文介绍了2022年的风口,探讨了一份稳定的副业收入对于普通人增加收入的重要性,以及如何抓住风口来实现赚钱的目标。文章指出,拼命工作并不一定能让人有钱,而是需要顺应时代的方向。 ... [详细]
  • MySQL中的MVVC多版本并发控制机制的应用及实现
    本文介绍了MySQL中MVCC的应用及实现机制。MVCC是一种提高并发性能的技术,通过对事务内读取的内存进行处理,避免写操作堵塞读操作的并发问题。与其他数据库系统的MVCC实现机制不尽相同,MySQL的MVCC是在undolog中实现的。通过undolog可以找回数据的历史版本,提供给用户读取或在回滚时覆盖数据页上的数据。MySQL的大多数事务型存储引擎都实现了MVCC,但各自的实现机制有所不同。 ... [详细]
author-avatar
小乌龟和小土鳖
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有