Javascript 继承机制的实现


Posted in Javascript onAugust 12, 2009

选定基类后,就可以创建它的子类了。是否使用基类完全由你决定。有时,你可能想创建一个不能直接使用的基类,它只是用于给子类提供通用的函数。在这种情况下,基类被看作抽象类。

尽管ECMAScript并没有像其他语言那样严格地定义抽象类,但有时它的确会创建一些不允许使用的类。通常,我们称这种类为抽象类。

创建的子类将继承超类的所有属性和方法,包括构造函数及方法的实现。记住,所有属性和方法都是公用的,因此子类可直接访问这些方法。子类还可添加超类中没有的新属性和方法,也可以覆盖超类中的属性和方法。

4.2.1 继承的方式

和其他功能一样,ECMAScript中实现继承的方式不止一种。这是因为JavaScript中的继承机制并不是明确规定的,而是通过模仿实现的。这意味着所有的继承细节并非由解释程序处理。作为开发者,你有权决定最适用的继承方式。

1. 对象冒充

构想原始的ECMAScript时,根本没打算设计对象冒充(object masquerading)。它是在开发者开始理解函数的工作方式,尤其是如何在函数环境中使用this关键字后才发展出来的。

其原理如下:构造函数使用this关键字给所有属性和方法赋值(即采用类声明的构造函数方式)。因为构造函数只是一个函数,所以可使ClassA的构造函数成为ClassB的方法,然后调用它。ClassB就会收到ClassA的构造函数中定义的属性和方法。例如,用下面的方式定义ClassA和ClassB:

Javascript 继承机制的实现

还记得吗?关键字this引用的是构造函数当前创建的对象。不过在这个方法中,this指向的是所属的对象。这个原理是把ClassA作为常规函数来建立继承机制,而不是作为构造函数。如下使用构造函数ClassB可以实现继承机制:

Javascript 继承机制的实现

Javascript 继承机制的实现在这段代码中,为ClassA赋予了方法newMethod(记住,函数名只是指向它的指针)。然后调用该方法,传递给它的是ClassB构造函数的参数sColor。最后一行代码删除了对ClassA的引用,这样以后就不能再调用它。

所有的新属性和新方法都必须在删除了新方法的代码行后定义。否则,可能会覆盖超类的相关属性和方法:

Javascript 继承机制的实现

为证明前面的代码有效,可以运行下面的例子:

Javascript 继承机制的实现

有趣的是,对象冒充可以支持多重继承。也就是说,一个类可以继承多个超类。用UML表示的多继承机制如图4-2所示。

Javascript 继承机制的实现

图  4-2

例如,如果存在两个类ClassX和ClassY,ClassZ想继承这两个类,可以使用下面的代码:

Javascript 继承机制的实现Javascript 继承机制的实现

Javascript 继承机制的实现

这里存在一个弊端,如果ClassX和ClassY具有同名的属性或方法,ClassY具有高优先级,因为继承的是最后的类。除这点小问题之外,用对象冒充实现多继承机制轻而易举。

由于这种继承方法的流行,ECMAScript的第三版为Function对象加入了两个新方法,即call()和apply()。

2. call()方法

call()方法是与经典的对象冒充方法最相似的方法。它的第一个参数用作this的对象。其他参数都直接传递给函数自身。例如:

Javascript 继承机制的实现

Javascript 继承机制的实现

在这个例子中,函数sayColor()在对象外定义,即使它不属于任何对象,也可以引用关键字this。对象obj的color属性等于"red"。调用call()方法时,第一个参数是obj,说明应该赋予sayColor()函数中的this关键字值是obj。第二个和第三个参数是字符串。它们与sayColor()函数中的参数prefix和suffix匹配,最后生成的消息"The color is red, a very nice color indeed"将被显示出来。

要与继承机制的对象冒充方法一起使用该方法,只需将前三行的赋值、调用和删除代码替换即可:

Javascript 继承机制的实现

Javascript 继承机制的实现这里,想让ClassA中的关键字this等于新创建的ClassB对象,因此this是第一个参数。第二个参数sColor对两个类来说都是唯一的参数。

3. apply()方法

apply()方法有两个参数,用作this的对象和要传递给函数的参数的数组。例如:

Javascript 继承机制的实现

这个例子与前面的例子相同,只是现在调用的是apply()方法。调用apply()方法时,第一个参数仍是obj,说明应该赋予sayColor()中的this关键字值是obj。第二个参数是由两个字符串构成的数组,与sayColor()的参数prefix和suffix匹配。生成的消息仍是"The color is red, a very nice color indeed",将被显示出来。

该方法也用于替换前三行的赋值、调用和删除新方法的代码:

Javascript 继承机制的实现

Javascript 继承机制的实现

同样的,第一个参数仍是this。第二个参数是只有一个值color的数组。可以把ClassB的整个arguments对象作为第二个参数传递给apply()方法:

Javascript 继承机制的实现

Javascript 继承机制的实现当然,只有超类中的参数顺序与子类中的参数顺序完全一致时才可以传递参数对象。如果不是,就必须创建一个单独的数组,按照正确的顺序放置参数。此外,还可使用call()方法。

4. 原型链

继承这种形式在ECMAScript中原本是用于原型链的。上一章介绍了定义类的原型方式。原型链扩展了这种方式,以一种有趣的方式实现继承机制。

在上一章中学过,prototype对象是个模板,要实例化的对象都以这个模板为基础。总而言之,prototype对象的任何属性和方法都被传递给那个类的所有实例。原型链利用这种功能来实现继承机制。

如果用原型方式重定义前面例子中的类,它们将变为下列形式:

Javascript 继承机制的实现

原型链的神奇之处在于突出显示的代码行。这里,把ClassB的prototype属性设置成ClassA的实例。这很有意义,因为想要ClassA的所有属性和方法,但又不想逐个将它们赋予ClassB的prototype属性。还有比把ClassA的实例赋予prototype属性更好的方法吗?

注意,调用ClassA的构造函数时,没有给它传递参数。这在原型链中是标准做法。要确保构造函数没有任何参数。

与对象冒充相似,子类的所有属性和方法都必须出现在prototype属性被赋值后,因为在它之前赋值的所有方法都会被删除。为什么?因为prototype属性被替换成了新对象,添加了新方法的原始对象将被销毁。所以,为ClassB类添加name属性和sayName()方法的代码如下:

Javascript 继承机制的实现Javascript 继承机制的实现

Javascript 继承机制的实现

可通过运行下面的例子测试这段代码:

Javascript 继承机制的实现

此外,在原型链中,instanceof运算符的运行方式也很独特。对ClassB的所有实例,instanceof为ClassA和ClassB都返回true。例如:

Javascript 继承机制的实现

在ECMAScript的弱类型世界中,这是极其有用的工具,不过使用对象冒充时不能使用它。

原型链的弊端是不支持多重继承。记住,原型链会用另一类型的对象重写类的prototype属性。

5. 混合方式

这种继承方式使用构造函数定义类,并未使用任何原型。对象冒充的主要问题是必须使用构造函数方式(如上一章中学到的),这不是最好的选择。不过如果使用原型链,就无法使用带参构造函数了。开发者该如何选择呢?答案很简单,两者都用。

在前一章,你学过创建类的最好方式是用构造函数方式定义属性,用原型方式定义方法。这种方式同样适用于继承机制,用对象冒充继承构造函数的属性,用原型链继承prototype对象的方法。用这两种方式重写前面的例子,代码如下:

Javascript 继承机制的实现Javascript 继承机制的实现

Javascript 继承机制的实现

在此例子中,继承机制由两行突出显示的代码实现。在第一行突出显示的代码中,在ClassB构造函数中,用对象冒充继承ClassA类的sColor属性。在第二行突出显示的代码中,用原型链继承ClassA类的方法。由于这种混合方式使用了原型链,所以instanceof运算符仍能正确运行。

下面的例子测试了这段代码:

Javascript 继承机制的实现

Javascript 相关文章推荐
JS location几个方法小姐
Jul 09 Javascript
浅析JavaScript中的同名标识符优先级
Dec 06 Javascript
jQuery中:checked选择器用法实例
Jan 04 Javascript
基于Jquery代码实现手风琴菜单
Nov 19 Javascript
详解Javascript函数声明与递归调用
Oct 22 Javascript
vue 中自定义指令改变data中的值
Jun 02 Javascript
vue 自定义提示框(Toast)组件的实现代码
Aug 17 Javascript
vue.js中toast用法及使用toast弹框的实例代码
Aug 27 Javascript
vue结合element-ui使用示例
Jan 24 Javascript
Node使用Selenium进行前端自动化操作的代码实现
Oct 10 Javascript
js中Function引用类型常见有用的方法和属性详解
Dec 11 Javascript
vue使用wavesurfer.js解决音频可视化播放问题
Apr 04 Vue.js
Javascript 继承实现例子
Aug 12 #Javascript
JQuery jsonp 使用示例代码
Aug 12 #Javascript
JavaScript 权威指南(第四版) 读书笔记
Aug 11 #Javascript
Javascript+XMLHttpRequest+asp.net无刷新读取数据库数据
Aug 09 #Javascript
javascript 当前日期加(天、周、月、年)
Aug 09 #Javascript
jQuery 解析xml文件
Aug 09 #Javascript
ext 代码生成器
Aug 07 #Javascript
You might like
傻瓜化配置PHP环境――Appserv
2006/12/13 PHP
destoon实现底部添加你是第几位访问者的方法
2014/07/15 PHP
四个常见html网页乱码问题及解决办法
2015/09/08 PHP
php中10个不同等级压缩优化图片操作示例
2016/11/14 PHP
基于jquery的web页面日期格式化插件
2011/11/15 Javascript
javascript中interval与setTimeOut的区别示例介绍
2014/03/14 Javascript
基于javascript、ajax、memcache和PHP实现的简易在线聊天室
2015/02/03 Javascript
NodeJS使用jQuery选择器操作DOM
2015/02/13 NodeJs
javascript十六进制及二进制转化的方法
2015/05/06 Javascript
jQuery实现自动调整字体大小的方法
2015/06/15 Javascript
JavaScript实现文本框中默认显示背景图片在获得焦点后消失的方法
2015/07/01 Javascript
JS模拟键盘打字效果的方法
2015/08/05 Javascript
jquery ajaxfileupload异步上传插件使用详解
2017/02/08 Javascript
vue-cli webpack 开发环境跨域详解
2017/05/18 Javascript
Bootstrap实现模态框效果
2019/09/30 Javascript
小程序实现tab标签页
2020/11/16 Javascript
[43:51]2014 DOTA2国际邀请赛中国区预选赛 Dream Times VS TongFu
2014/05/22 DOTA
[01:37]全新的一集《真视界》——TI7总决赛
2017/09/21 DOTA
Python操作json数据的一个简单例子
2014/04/17 Python
Python2.x中文乱码问题解决方法
2015/06/02 Python
Django的URLconf中使用缺省视图参数的方法
2015/07/18 Python
Python中的列表生成式与生成器学习教程
2016/03/13 Python
python实现单链表中删除倒数第K个节点的方法
2018/09/28 Python
Python面向对象程序设计OOP入门教程【类,实例,继承,重载等】
2019/01/05 Python
浅谈python已知元素,获取元素索引(numpy,pandas)
2019/11/26 Python
python对文件的操作方法汇总
2020/02/28 Python
解决pytorch 数据类型报错的问题
2021/03/03 Python
金融专业应届生求职信
2013/11/02 职场文书
实现中国梦思想汇报2014
2014/09/13 职场文书
离婚协议书包括哪些内容
2014/10/16 职场文书
2014年司法所工作总结
2014/11/22 职场文书
医院营销工作计划
2015/01/16 职场文书
行政助理岗位职责范本
2015/04/11 职场文书
简历中的自我评价怎么写呢?
2019/04/30 职场文书
实例详解Python的进程,线程和协程
2022/03/13 Python
flex布局中使用flex-wrap实现换行的项目实践
2022/06/21 HTML / CSS