JavaScript中的类继承


Posted in Javascript onNovember 25, 2010

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 < arguments.length; i += 1) { 
var name = arguments[i]; 
this.prototype[name] = parent.prototype[name]; 
} 
return this; 
});

The swiss方法对每个参数进行循环。每个名称,它都将parent的原型中的成员复制下来到新的类的prototype中。
总结
JavaScript可以像类语言那样使用,但它也有一种十分独特的表现层次。我们已经看过了类继承、瑞士继承、寄生继承、类扩展和对象扩展。这一等系列代码复用的模式都能来自这个一直被认为是很小、很简单的JavaScript语言。
类对象属于“硬的”。给一个“硬的”对象添加成员的唯一的方法是建立一个新的类。在JavaScript中,对象是“软的”。要给一个“软”对象添加成员只要简单的赋值就行了。
因为JavaScript中的类是这样地灵活,你可能会还想到更复杂的类继承。但深度继承并不合适。浅继承则较有效而且更易表达。
Javascript 相关文章推荐
AlertBox 弹出层信息提示框效果实现步骤
Oct 11 Javascript
JavaScript对象之间的转换 jQuery对象和原声DOM
Mar 07 Javascript
JavaScript面向对象程序设计三 原型模式(上)
Dec 21 Javascript
JavaScript中标识符提升问题
Jun 11 Javascript
jQuery实现复选框批量选择与反选的方法
Jun 17 Javascript
jQuery+css3实现文字跟随鼠标的上下抖动
Jul 31 Javascript
jQuery实现下拉菜单(内容为时间)的实时更新及图表的随动更新的方法
Jul 07 Javascript
AngularJS基础 ng-init 指令简单示例
Aug 02 Javascript
Angular 4.0学习教程之架构详解
Sep 12 Javascript
JavaScript实现获取select下拉框中第一个值的方法
Feb 06 Javascript
详解Vue组件之间通信的七种方式
Apr 14 Javascript
详解如何探测小程序返回到webview页面
May 14 Javascript
js getBoundingClientRect() 来获取页面元素的位置
Nov 25 #Javascript
腾讯的ip接口 方便获取当前用户的ip地理位置
Nov 25 #Javascript
js删除所有的cookie的代码
Nov 25 #Javascript
javascript 广告后加载,加载完页面再加载广告
Nov 25 #Javascript
js页面跳转常用的几种方式
Nov 25 #Javascript
简略的前端架构心得&amp;&amp;基于editor为例子的编码小技巧
Nov 25 #Javascript
javascript showModalDialog 内跳转页面的问题
Nov 25 #Javascript
You might like
PHP与SQL注入攻击[二]
2007/04/17 PHP
php面向对象全攻略 (六)__set() __get() __isset() __unset()的用法
2009/09/30 PHP
PHP中通过HTTP_USER_AGENT判断是否为手机移动终端的函数代码
2013/02/14 PHP
数组与类使用PHP的可变变量名需要的注意的问题
2013/06/20 PHP
PHP面向对象之旅:深入理解static变量与方法
2014/01/06 PHP
浅析ThinkPHP缓存之快速缓存(F方法)和动态缓存(S方法)(日常整理)
2015/10/26 PHP
jquery.simple.tree插件 更简单,兼容性更好的无限树插件
2010/09/03 Javascript
ko knockoutjs动态属性绑定技巧应用
2012/11/14 Javascript
利用jquery写的左右轮播图特效
2014/02/12 Javascript
教你用jquery实现iframe自适应高度
2014/06/11 Javascript
jQuery.each使用详解
2015/07/07 Javascript
JavaScript中的prototype原型学习指南
2016/05/09 Javascript
JavaScript编写Chrome扩展实现与浏览器的交互及时间通知
2016/05/16 Javascript
JS 日期与时间戮相互转化的简单实例
2016/06/22 Javascript
Bootstrap零基础学习第一课之模板
2016/07/18 Javascript
深入分析node.js的异步API和其局限性
2016/09/05 Javascript
Angular2 环境配置详细介绍
2016/09/21 Javascript
js实现文字列表无缝滚动效果
2017/06/23 Javascript
Angularjs中的$apply及优化使用详解
2018/07/02 Javascript
echarts大屏字体自适应的方法步骤
2019/07/12 Javascript
layui实现给某一列加点击事件
2019/10/26 Javascript
Vue在chrome44偶现点击子元素事件无法冒泡的解决方法
2019/12/15 Javascript
nodejs nedb 封装库与使用方法示例
2020/02/06 NodeJs
[02:28]DOTA2 2015国际邀请赛中国区预选赛首日现场百态
2015/05/26 DOTA
Python高效编程技巧
2013/01/07 Python
快速排序的算法思想及Python版快速排序的实现示例
2016/07/02 Python
浅谈python中的__init__、__new__和__call__方法
2017/07/18 Python
uwsgi+nginx部署Django项目操作示例
2018/12/04 Python
Python格式化字符串f-string概览(小结)
2019/06/18 Python
Python tornado上传文件的功能
2020/03/26 Python
个人求职信范文分享
2013/12/13 职场文书
母亲七十大寿答谢词
2014/01/18 职场文书
校本教研工作制度
2014/01/22 职场文书
计算机毕业生自荐信范文
2014/03/23 职场文书
五星级酒店前台接待岗位职责
2015/04/02 职场文书
pytorch Dropout过拟合的操作
2021/05/27 Python