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 相关文章推荐
jquery禁用右键单击功能屏蔽F5刷新
Mar 17 Javascript
JavaScript实现大数的运算
Nov 24 Javascript
JS动态显示表格上下frame的方法
Mar 31 Javascript
jQuery Mobile动态刷新页面样式的实现方法
May 28 Javascript
jQuery弹出div层过2秒自动消失
Nov 29 Javascript
Bootstrap基本组件学习笔记之进度条(15)
Dec 08 Javascript
TypeScript学习之强制类型的转换
Dec 27 Javascript
vue实现一个炫酷的日历组件
Oct 08 Javascript
微信小程序左滑删除功能开发案例详解
Nov 12 Javascript
微信小程序实现手势滑动效果
Aug 26 Javascript
javascript的hashCode函数实现代码小结
Aug 11 Javascript
vue中activated的用法
Jan 03 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
短波收音机简介
2021/03/01 无线电
解决MySQL中文输出变成问号的问题
2008/06/05 PHP
php比较相似字符串的方法
2015/06/05 PHP
PHP中生成UUID自定义函数分享
2015/06/10 PHP
CodeIgniter表单验证方法实例详解
2016/03/03 PHP
利用js 进行输入框自动匹配字符的小例子
2013/06/29 Javascript
JS返回iframe中frameBorder属性值的方法
2015/04/01 Javascript
简介JavaScript中strike()方法的使用
2015/06/08 Javascript
分享Javascript实用方法二
2015/12/13 Javascript
bootstrap 表单验证使用方法
2017/01/11 Javascript
react.js 翻页插件实例代码
2017/01/19 Javascript
如何写好你的JavaScript【推荐】
2017/03/02 Javascript
cordova入门基础教程及使用中遇到的一些问题总结
2017/11/14 Javascript
微信小程序Getuserinfo解决方案图解
2018/08/24 Javascript
微信小程序使用setData修改数组中单个对象的方法分析
2018/12/30 Javascript
vue 解决在微信内置浏览器中调用支付宝支付的情况
2020/11/09 Javascript
js删除指定位置超链接中含有百度与360的标题
2021/01/06 Javascript
[43:24]完美世界DOTA2联赛PWL S3 INK ICE vs DLG 第二场 12.12
2020/12/17 DOTA
Python中的异常处理相关语句基础学习笔记
2016/07/11 Python
python实现读取并显示图片的两种方法
2017/01/13 Python
详解 Python中LEGB和闭包及装饰器
2017/08/03 Python
Python内置函数——__import__ 的使用方法
2017/11/24 Python
Python分割指定页数的pdf文件方法
2018/10/26 Python
python之pymysql模块简单应用示例代码
2019/12/16 Python
用CSS3来实现社交分享按钮
2014/11/11 HTML / CSS
使用HTML5原生对话框元素并轻松创建模态框组件
2019/03/06 HTML / CSS
巴西宠物商店:Cobasi
2019/04/19 全球购物
德国二手设计师时装和复古时装跳蚤市场:Mädchenflohmarkt
2020/11/09 全球购物
如何提高MySql的安全性
2014/06/19 面试题
《日月潭》教学反思
2014/02/28 职场文书
欢迎横幅标语
2014/06/17 职场文书
新党章心得体会
2014/09/04 职场文书
解除劳动合同协议书范本2014
2014/09/25 职场文书
新年晚会开场白
2015/05/29 职场文书
如何将JavaScript将数组转为树形结构
2021/06/02 Javascript
手写Spirit防抖函数underscore和节流函数lodash
2022/03/22 Javascript