继承机制的实现

要用 ECMAScript 实现继承机制,您可以从要继承的基类入手。所有开发者定义的类都可作为基类。出于安全原因,本地类和宿主类不能作为基类,这样可以防止公用访问编译过的浏览器级的代码,因为这些代码可以被用于恶意攻击。

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

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

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

继承的方式

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

下面为您介绍几种具体的继承方式。

对象冒充

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

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

function ClassA(sColor) {this.color = sColor;this.sayColor = function () {alert(this.color);};
}function ClassB(sColor) {
}

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

function ClassB(sColor) {this.newMethod = ClassA;this.newMethod(sColor);delete this.newMethod;
}

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

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

function ClassB(sColor, sName) {this.newMethod = ClassA;this.newMethod(sColor);delete this.newMethod;this.name = sName;this.sayName = function () {alert(this.name);};
}

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

var objA = new ClassA("blue");
var objB = new ClassB("red", "John");
objA.sayColor();    //输出 "blue"
objB.sayColor();    //输出 "red"
objB.sayName(); //输出 "John"

对象冒充可以实现多重继承

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

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

function ClassZ() {this.newMethod = ClassX;this.newMethod();delete this.newMethod;this.newMethod = ClassY;this.newMethod();delete this.newMethod;
}

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

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

call() 方法

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

function sayColor(sPrefix,sSuffix) {alert(sPrefix + this.color + sSuffix);
};var obj = new Object();
obj.color = "blue";sayColor.call(obj, "The color is ", "a very nice color indeed.");

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

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

function ClassB(sColor, sName) {//this.newMethod = ClassA;//this.newMethod(color);//delete this.newMethod;ClassA.call(this, sColor);this.name = sName;this.sayName = function () {alert(this.name);};
}

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

apply() 方法

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

function sayColor(sPrefix,sSuffix) {alert(sPrefix + this.color + sSuffix);
};var obj = new Object();
obj.color = "blue";sayColor.apply(obj, new Array("The color is ", "a very nice color indeed."));

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

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

function ClassB(sColor, sName) {//this.newMethod = ClassA;//this.newMethod(color);//delete this.newMethod;ClassA.apply(this, new Array(sColor));this.name = sName;this.sayName = function () {alert(this.name);};
}

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

function ClassB(sColor, sName) {//this.newMethod = ClassA;//this.newMethod(color);//delete this.newMethod;ClassA.apply(this, arguments);this.name = sName;this.sayName = function () {alert(this.name);};
}

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

原型链(prototype chaining)

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

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

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

function ClassA() {
}ClassA.prototype.color = "blue";
ClassA.prototype.sayColor = function () {alert(this.color);
};function ClassB() {
}ClassB.prototype = new ClassA();

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

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

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

function ClassB() {
}ClassB.prototype = new ClassA();ClassB.prototype.name = "";
ClassB.prototype.sayName = function () {alert(this.name);
};

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

var objA = new ClassA();
var objB = new ClassB();
objA.color = "blue";
objB.color = "red";
objB.name = "John";
objA.sayColor();
objB.sayColor();
objB.sayName();

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

var objB = new ClassB();
alert(objB instanceof ClassA);  //输出 "true"
alert(objB instanceof ClassB);  //输出 "true"

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

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

混合方式

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

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

function ClassA(sColor) {this.color = sColor;
}ClassA.prototype.sayColor = function () {alert(this.color);
};function ClassB(sColor, sName) {ClassA.call(this, sColor);this.name = sName;
}ClassB.prototype = new ClassA();ClassB.prototype.sayName = function () {alert(this.name);
};

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

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

var objA = new ClassA("blue");
var objB = new ClassB("red", "John");
objA.sayColor();    //输出 "blue"
objB.sayColor();    //输出 "red"
objB.sayName(); //输出 "John"

转载声明: 本文转自 http://www.w3school.com.cn/js/pro_js_inheritance_implementing.asp (W3School)

转载于:https://www.cnblogs.com/mixer/archive/2010/09/02/2448171.html

ECMAScript 继承机制实现相关推荐

  1. JavaScript实现继承机制(3)——通过原型链(prototype chaining)方式

    我们知道在JavaScript中定义类的原型方式,而原型链扩展了这种方式,以一种有趣的方式实现继承机制. prototype 对象是个模板,要实例化的对象都以这个模板为基础.总而言之,prototyp ...

  2. 继承机制中的构造器和析构器 - C++快速入门17

    继承机制中的构造器和析构器 让编程改变世界 Change the world by program 继承机制中的构造器和析构器 正如此前所讲解的,C++ 支持程序员自己写出将创建或销毁一个对象时自动调 ...

  3. 深入浅出理解Javascript原型概念以及继承机制(转)

    在Javascript语言中,原型是一个经常被讨论到但是有非常让初学者不解的概念.那么,到底该怎么去给原型定义呢?不急,在了解是什么之前,我们不妨先来看下为什么. Javascript最开始是网景公司 ...

  4. Javascript继承机制的设计思想

    我一直很难理解Javascript语言的继承机制. 它没有"子类"和"父类"的概念,也没有"类"(class)和"实例" ...

  5. c语言继承机制的作用,计算机二级C++强化试题及答案

    计算机二级C++强化试题及答案 通过试题的练习,能够帮助考生们掌握所学知识点,下面是小编为大家整理的计算机二级C++强化试题及答案,欢迎参考~ 计算机二级C++强化试题及答案 1). 循环链表的主要优 ...

  6. 模拟电梯2.0(继承机制实验)

    浙江理工大学信息学院 实验指导书 实验名称:类的继承机制的实现 学时安排:3 实验类别:设计性实验 实验要求:1人1组  ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ...

  7. c++ 继承机制易犯的错误

    继承作为面向对象的基本特征之一,其使用率极高.不管是为了实现软件的基本功能,还是再程序的重构的过程中,我们总是会用到继承机制.正是因为其用途极为广泛,而且使用简单,大众程序员对其真正的内部实现机制的探 ...

  8. javascript 对象基础 继承机制实例【对象冒充】

    面向对象语言 必须支持继承机制,既一个类能重用另一个类的方法和属性. 1.继承方式 对象冒充 工作原理:构造函数使用this关键字,给所有属性和方法赋值,因为构造函数值只是一个函数,所以可以使Clas ...

  9. 在java继承机制中 父类中的私有_Java中子类能继承父类的私有属性吗?

    前段时间去听老师讲课的时候,老师告诉我子类是可以继承父类所有的属性和方法的.当时我是极其疑惑的,因为之前学校考试时这个考点我记得很清楚:子类只能继承父类的非私有属性和方法.老师给我的解释是这样的--先 ...

最新文章

  1. keras 的 example 文件 cnn_seq2seq.py 解析
  2. java搭载服务_Java入门及环境搭载
  3. GRUB2 管理器—Grub Customizer
  4. QT的QShortcut类的使用
  5. QT的QDesignerCustomWidgetInterface类的使用
  6. javafx css_JavaFX技巧13:研究Modena CSS文件
  7. 如何将JBoss HR员工奖励项目放入云端
  8. Docker1.8在Centos7下的安装
  9. Java多线程,锁(synchronize),饿汉式单例线程,等待处理机制
  10. python里自带的绘画库是_Python绘图Turtle库详解
  11. 1163 最高的奖励(贪心+优先队列)
  12. 入门 | 神经网络词嵌入:如何将《战争与和平》表示成一个向量?
  13. 2022年低功耗软路由cpu一览
  14. 脸上长了黄褐斑怎么办
  15. 数字逻辑电路(一、基本概述、数制及其转换)
  16. 元音I、i 、ε、æ、a小总结
  17. SpringCloud 之 注册中心
  18. js中RGB与十六进制颜色转换
  19. Ryzen3 2200g安装Debian11.1后配置VBox
  20. Nvjdc(诺兰)再续经典——诺兰方舟ARK

热门文章

  1. SpringBoot配置属性之DataSource
  2. pytorch--nn.Sequential学习
  3. 经常遇到的浏览器的兼容性有哪些?原因?解决方法是什么?
  4. Android 隐藏Home键、返回键
  5. artdialog 异步加载页面 生成验证码
  6. 文件夹目录结构导出工具及下载
  7. Function in loop and closure
  8. rails笔记 cache系统
  9. Win64 驱动内核编程-19.HOOK-SSDT
  10. 虚拟桌面模拟查找点击自绘控件