Javascript 继承机制的实现

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

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

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

4.2.1 继承的方式

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

1. 对象冒充

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

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

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

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

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

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

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

图  4-2

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

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

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

2. call()方法

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

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

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

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

3. apply()方法

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

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

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

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

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

4. 原型链

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

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

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

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

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

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

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

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

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

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

5. 混合方式

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

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

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

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

(0)

相关推荐

  • javascript类继承机制的原理分析

    目前 javascript的实现继承方式并不是通过"extend"关键字来实现的,而是通过 constructor function和prototype属性来实现继承.首先我们创建一个animal 类 js 代码 复制代码 代码如下: var animal = function (){ //这就是constructor function 了 this .name = 'pipi'; this .age = 10; this .height = 0; } //建立一个动物的实例 var

  • 基于JavaScript实现继承机制之构造函数+原型链混合方式的使用详解

    构造函数.原型实现继承的缺陷 首先来分析构造函数和原型链两种实现继承方式的缺陷: 构造函数(对象冒充)的主要问题是必须使用构造函数方式,且无法继承通过原型定义的方法,这不是最好的选择.不过如果使用原型链,就无法使用带参数的构造函数了.开发者如何选择呢?答案很简单,两者都用. 构造函数+原型混合方式 这种继承方式使用构造函数定义类,并非使用任何原型.创建类的最好方式是用构造函数定义属性,用原型定义方法.这种方式同样适用于继承机制,用对象冒充继承构造函数的属性,用原型链继承 prototype 对象

  • JavaScript 继承机制的实现(待续)

    1.对象冒充 原理:构造函数使用this关键字给所有属性和方法赋值(即采用类声明的构造函数方式). 因为构造函数只是一个函数,所以可使ClassA的构造函数成为ClassB的方法,然后调用它.ClassB就会收到ClassA的构造函数中定义的属性和方法. 例如: 下面方式定义的ClassA和ClassB: 复制代码 代码如下: function ClassA(sColor){ this.color=sColor; this.sayColor=function(){ alert(this.colo

  • 由JavaScript中call()方法引发的对面向对象继承机制call的思考

    起因: 今天在阅读snandy大神的读jQuery之五(取DOM元素)时,看到有讲到toArray()方法,具体jQuery代码如下: 复制代码 代码如下: toArray: function() { return slice.call( this, 0 ); }, get: function( num ) { return num == null ? // Return a 'clean' array this.toArray() : // Return just the object ( n

  • Javascript继承机制的设计思想分享

    我一直很难理解Javascript语言的继承机制. 它没有"子类"和"父类"的概念,也没有"类"(class)和"实例"(instance)的区分,全靠一种很奇特的"原型链"(prototype chain)模式,来实现继承. 我花了很多时间,学习这个部分,还做了很多笔记.但是都属于强行记忆,无法从根本上理解. 直到昨天,我读到法国程序员Vjeux的解释,才恍然大悟,完全明白了Javascript为什么这样

  • 基于JavaScript实现继承机制之原型链(prototype chaining)的详解

    如果用原型方式重定义前面例子中的类,它们将变为下列形式: 复制代码 代码如下: function ClassA() {} ClassA.prototype.color = "blue";ClassA.prototype.sayColor = function () {    alert(this.color);}; function ClassB() {} ClassB.prototype = new ClassA(); 原型方式的神奇之处在于最后一行代码.这里,把 ClassB 的

  • 基于JavaScript实现继承机制之调用call()与apply()的方法详解

    call() 方法call() 方法是与经典的对象冒充方法最相似的方法.它的第一个参数用作 this 的对象.其他参数都直接传递给函数自身.例如: 复制代码 代码如下: function sayHello(sPrefix,sSuffix) {    alert(this.name + "says" + sPrefix + sSuffix);}; var obj = new Object();obj.name = "Tom"; sayHello.call(obj, &

  • Javascript继承机制详解

    学完了Javascript类和对象的创建之后,现在总结一下Javascript继承机制的实现.Javascript并不像Java那样对继承机制有严格明确的定义,它的实现方式正如它的变量的使用方式那样也是十分宽松的,你可以设计自己的方法"模仿"继承机制的实现.有以下几种方法: 1.对象冒充 <script type="text/javascript"> function classA(str){ this.str=str; this.printstr=fu

  • javascript继承机制实例详解

    本文实例讲述了javascript继承机制.分享给大家供大家参考.具体分析如下: 初学javascript一般很难理解Javascript语言的继承机制它没有"子类"和"父类"的概念,也没有"类"(class)和"实例"(instance)的区分,全靠一种很奇特的"原型链"(prototype chain)模式,来实现继承. 我花了很多时间,学习这个部分,还做了很多笔记.但是都属于强行记忆,无法从根本上理解

  • 由浅入深讲解Javascript继承机制与simple-inheritance源码分析

    老生常谈的问题,大部分人也不一定可以系统的理解.Javascript语言对继承实现的并不好,需要工程师自己去实现一套完整的继承机制.下面我们由浅入深的系统掌握使用javascript继承的技巧. 1. 直接使用原型链 这是最简粗暴的一种方式,基本没法用于具体的项目中.一个简单的demo如下: function SuperType(){ this.property = true; } SuperType.prototype.getSuperValue = function(){ return th

  • Javascript 继承机制的实现

    选定基类后,就可以创建它的子类了.是否使用基类完全由你决定.有时,你可能想创建一个不能直接使用的基类,它只是用于给子类提供通用的函数.在这种情况下,基类被看作抽象类. 尽管ECMAScript并没有像其他语言那样严格地定义抽象类,但有时它的确会创建一些不允许使用的类.通常,我们称这种类为抽象类. 创建的子类将继承超类的所有属性和方法,包括构造函数及方法的实现.记住,所有属性和方法都是公用的,因此子类可直接访问这些方法.子类还可添加超类中没有的新属性和方法,也可以覆盖超类中的属性和方法. 4.2.

  • 阿里巴巴技术文章分享 Javascript继承机制的实现

    Javascript作为一门脚本语言,在设计之初并没有考虑到面向对象的特性.即便到了当今这个遍布现代浏览器的年代,各种Javascript 框架/库如雨后春笋般地疯狂生长,Javascript中连个 class 关键字都没有.如果你要编写一个类,你还得借助于function,至于继承.重载什么的,就别奢望了. 可是,没有继承,日子怎么过啊?难道把所有的共有逻辑都拷贝一遍,实现最低级的代码复用? 答案当然是--NO,所以,我们要自己实现继承! 目标 最关键的目标当然是继承--子类自动拥有父类的所有

  • Javascript 继承机制实例

    实际上,几何形状只有两种,即椭圆形(是圆形的)和多边形(具有一定数量的边).圆是椭圆形的一种,它只有一个焦点.三角形.矩形和五边形都是多边形的一种,具有不同数量的边.正方形是矩形的一种,所有的边等长.这就构成了一种完美的继承关系. 在这个例子中,形状(Shape)是椭圆形(Ellipse)和多边形(Polygon)的基类(base class)(所有类都由它继承而来).椭圆具有一个属性foci,说明椭圆具有的焦点的个数.圆形(Circle)继承了椭圆形,因此圆形是椭圆形的子类(subclass)

  • Javascript继承(上)——对象构建介绍

    Javascript中存在"类"么? 万物皆对象 Javascript中除了基本数据(Undefined.Null.Boolean.Number.String),其他都是对象(Object). 实际上,Javascript中的对象是数据与功能的集合.例如我们知道: 复制代码 代码如下: var foo = new Function("alert('hello world!')"); foo(); 可见foo是一个函数,也是一种对象.再比如说: 复制代码 代码如下:

随机推荐