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 相关文章推荐
js 控制下拉菜单刷新的方法
Mar 03 Javascript
javascript中比较字符串是否相等的方法
Jul 23 Javascript
javaScript实现浮点数转十六进制字符
Oct 29 Javascript
jquery插件开发之实现jquery手风琴功能分享
Mar 10 Javascript
Bootstrap导航菜单点击后无法自动添加active的处理方法
Aug 10 Javascript
jquery操作checkbox的常用方法总结【附测试源码下载】
Jun 10 jQuery
Vue实现点击按钮复制文本内容的例子
Nov 09 Javascript
JS中作用域以及变量范围分析
Jul 18 Javascript
详解VUE中的插值( Interpolation)语法
Oct 18 Javascript
Vue实现简单购物车功能
Dec 13 Vue.js
vue实现桌面向网页拖动文件的示例代码(可显示图片/音频/视频)
Mar 01 Vue.js
Vue2.x-使用防抖以及节流的示例
Mar 02 Vue.js
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 开发环境配置(测试开发环境)
2010/04/28 PHP
PHP中如何调用webservice的实例参考
2013/04/25 PHP
php连接Access数据库错误及解决方法
2013/06/20 PHP
PHP生成数组再传给js的方法
2014/08/07 PHP
php操作xml入门之xml标签的属性分析
2015/01/23 PHP
php文件夹的创建与删除方法
2015/01/24 PHP
php创建多级目录的方法
2015/03/24 PHP
Javascript实例教程(19) 使用HoTMetal(3)
2006/12/23 Javascript
javascript操作文本框readOnly
2007/05/15 Javascript
javascript的trim,ltrim,rtrim自定义函数
2008/09/21 Javascript
基于Jquery 解决Ajax请求的页面 浏览器后退前进功能,页面刷新功能实效问题
2010/12/11 Javascript
JQuery中getJSON的使用方法
2010/12/13 Javascript
js中substring和substr的详细介绍与用法
2013/08/29 Javascript
JS短路原理的应用示例 精简代码的途径
2013/12/13 Javascript
jquery 中ajax执行的优先级
2015/06/22 Javascript
js jquery获取当前元素的兄弟级 上一个 下一个元素
2015/09/01 Javascript
jQuery EasyUI中DataGird动态生成列的方法
2016/04/05 Javascript
AngularJS基础 ng-mouseover 指令简单示例
2016/08/02 Javascript
JS+CSS3模拟溢出滚动效果
2016/08/12 Javascript
Javascript从数组中随机取出不同元素的两种方法
2016/09/22 Javascript
Jquery Easyui搜索框组件SearchBox使用详解(19)
2016/12/17 Javascript
微信小程序实现的贪吃蛇游戏【附源码下载】
2018/01/03 Javascript
axios全局注册,设置token,以及全局设置url请求网段的方法
2018/09/25 Javascript
vuex 中插件的编写案例解析
2019/06/10 Javascript
详解js location.href和window.open的几种用法和区别
2019/12/02 Javascript
Python(Tornado)模拟登录小米抢手机
2013/11/12 Python
Python的Django框架使用入门指引
2015/04/15 Python
11月编程语言排行榜 Python逆袭C#上升到第4
2017/11/15 Python
详解Python3 pickle模块用法
2019/09/16 Python
python基于plotly实现画饼状图代码实例
2019/12/16 Python
浅谈keras的深度模型训练过程及结果记录方式
2020/01/24 Python
施工班组长岗位职责
2014/01/05 职场文书
小学绿色学校申报材料
2014/08/23 职场文书
教师党员个人整改措施材料
2014/09/16 职场文书
2014年度个人工作总结
2014/11/07 职场文书
创业计划书之情侣餐厅
2019/09/29 职场文书