首先,我们用一个经典例子来简单阐述一下ECMAScript中的继承机制。

在几何学上,实质上几何形状只有两种,即椭圆形(是圆形的)和多边形(具有一定数量的边)。圆是椭圆的一种,它只有一个焦点。三角形、矩形和五边形都是多边形的一种,具有不同数量的边。正方形是矩形的一种,所有的边等长。这就构成了一种完美的继承关系。如下图:

在这个例子中,形状(Shape)是椭圆形(Ellipse)和多边形(Polygon)的基类(base class)(所有类都由它继承而来)。椭圆具有一个属性 foci,说明椭圆具有的焦点的个数。圆形(Circle)继承了椭圆形,因此圆形是椭圆形的子类(subclass),椭圆形是圆形的超类(superclass)。同样,三角形(Triangle)、矩形(Rectangle)和五边形(Pentagon)都是多边形的子类,多边形是它们的超类。最后,正方形(Square)继承了矩形。

要用 ECMAScript 实现继承机制,您可以从要继承的基类入手。所有开发者定义的类都可作为基类。出于安全原因,本地类和宿主类不能作为基类,这样可以防止公用访问编译过的浏览器级的代码,因为这些代码可以被用于恶意攻击。选定基类后,就可以创建它的子类了。是否使用基类完全由你决定。有时,你可能想创建一个不能直接使用的基类,它只是用于给子类提供通用的函数。在这种情况下,基类被看作抽象类。尽管 ECMAScript 并没有像其他语言那样严格地定义抽象类,但有时它的确会创建一些不允许使用的类。通常,我们称这种类为抽象类。创建的子类将继承超类的所有属性和方法,包括构造函数及方法的实现。记住,所有属性和方法都是公用的,因此子类可直接访问这些方法。子类还可添加超类中没有的新属性和方法,也可以覆盖超类的属性和方法。

虽然我们在上面用到了"类"这个名词,但实际上ECMAScript中并没有对类的定义。我们只是为了能够形象地描述继承机制,才如此而已。ECMAScript 实现继承的方式不止一种。这是因为 JavaScript 中的继承机制并不是明确规定的,而是通过模仿实现的。这意味着所有的继承细节并非完全由解释程序处理。作为开发者,你有权决定最适用的继承方式。

对象冒充

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

01 function ClassA(sColor)
02 {
03     this.color = sColor;
04     this.sayColor = function () {
05         alert(this.color);
06     };
07 }
08 function ClassB(sColor, sName)
09 {
10     this.newMethod = ClassA;   //为ClassB添加新方法newMethod,该方法引用ClassA
11     this.newMethod(sColor);   
12     delete this.newMethod; //为了避免影响ClassA的属性和方法,将该方法销毁。因为函数ClassA只是引用值
13    
14     this.name = sName;
15     this.sayName = function ()
16     {
17         alert(this.name);
18     };   
19 }
20    
21 var objA = new ClassA("blue");
22 var objB = new ClassB("red", "John");
23 objA.sayColor(); //弹出"blue"
24 objB.sayColor(); //弹出"red"
25 objB.sayName(); //弹出"John"

构造函数使用 this 关键字给所有属性和方法赋值(即采用类声明的构造函数方式)。因为构造函数只是一个函数,所以可使 ClassA 构造函数成为 ClassB 的方法,然后调用它。ClassB 就会收到 ClassA 的构造函数中定义的属性和方法。在这个方法中,把ClassA作为常规函数来建立继承机制,而不是作为构造函数。为ClassB添加newMethod方法,该方法引用ClassA(函数名只是指向它的指针)。然后调用该方法,传递给它的是 ClassB 构造函数的参数 sColor。最后一行代码删除了对 ClassA 的引用,这样以后就不能再调用它。所有新属性和新方法都必须在上传了新方法的代码行后定义。否则可能会覆盖超累的相关属性和方法。

对象冒充可以实现多重继承。例如,如果存在两个类 ClassX 和 ClassY,ClassZ 想继承这两个类,可以使用下面的代码:

1 function ClassZ() {
2     this.newMethod = ClassX;
3     this.newMethod();
4     delete this.newMethod;
5     this.newMethod = ClassY;
6     this.newMethod();
7     delete this.newMethod;
8 }

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

call() 方法

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

1 function sayColor(sPrefix,sSuffix)
2 {
3     alert(sPrefix + this.color + sSuffix);
4 };
5    
6 var obj = new Object();
7 obj.color = "blue";
8    
9 sayColor.call(obj, "The color is ", "a very nice color indeed.");

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

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

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

1 function ClassB(sColor, sName) {
2     //this.newMethod = ClassA;
3     //this.newMethod(color);
4     //delete this.newMethod;
5     ClassA.call(this, sColor);
6     this.name = sName;
7     this.sayName = function () {
8         alert(this.name);
9     };

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

apply()方法

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

1 function sayColor(sPrefix,sSuffix) {
2     alert(sPrefix + this.color + sSuffix);
3 };
4 var obj = new Object();
5 obj.color = "blue";
6 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.",将被显示出来。

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

1 function ClassB(sColor, sName) {
2     //this.newMethod = ClassA;//this.newMethod(color);//delete this.newMethod;
3     ClassA.apply(this, new Array(sColor));
4     this.name = sName;
5     this.sayName = function () {
6         alert(this.name);
7     };
8 }

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

1 function ClassB(sColor, sName) {
2     //this.newMethod = ClassA;//this.newMethod(color);//delete this.newMethod;
3     ClassA.apply(this, arguments);
4     this.name = sName;
5     this.sayName = function () {
6         alert(this.name);
7     };
8 }

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

原型链

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

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

01 function ClassA(sColor) {
02     this.color = sColor;
03 }
04    
05 ClassA.prototype.sayColor = function () {
06     alert(this.color);
07 };
08    
09 function ClassB(sColor, sName) {
10     ClassA.call(this, sColor);
11     this.name = sName;
12 }
13    
14 //ClassB.prototype = ClassA.prototype;
15 ClassB.prototype = new ClassA(); //调用 ClassA 的构造函数,没有给它传递参数。这在原型链中是标准做法。要确保构造函数没有任何参数
16 ClassB.prototype.sayName = function () {
17     alert(this.name);
18 };
19    
20 var objA = new ClassA("blue");
21 var objB = new ClassB("red", "John");
22 objA.sayColor();
23 24objB.sayColor();
24 25objB.sayName();

在 ECMAScript 的弱类型世界中,这是极其有用的工具,不过使用对象冒充时不能使用它。原型链的弊端是不支持多重继承。记住,原型链会用另一类型的对象重写类的 prototype 属性。

混合方式

这种继承方式使用构造函数定义类,并非使用任何原型。对象冒充的主要问题是必须使用构造函数方式,这不是最好的选择。不过如果使用原型链,就无法使用带参数的构造函数了。开发者如何选择呢?答案很简单,两者都用。是用构造函数定义属性,用原型定义方法。这种方式同样适用于继承机制,用对象冒充继承构造函数的属性,用原型链继承 prototype 对象的方法。用这两种方式重写前面的例子,代码如下:

01 function ClassA(sColor) {
02     this.color = sColor;
03 }
04 ClassA.prototype.sayColor = function () {
05     alert(this.color);
06 };
07    
08 function ClassB(sColor, sName) {
09     ClassA.call(this, sColor);     // 在 ClassB 构造函数中,用对象冒充继承 ClassA 类的 sColor 属性
10     this.name = sName;
11 }
12    
13 ClassB.prototype = new ClassA();   //用原型链继承 ClassA 类的方法
14    
15 ClassB.prototype.sayName = function () {
16     alert(this.name);
17 };

实际上ECMAScript中并没有对类的定义的更多相关文章

  1. ECMAScript中关于如何获取this的定义

    文章中一些名词的翻译存疑,没有查过正式的中文名称 前面都是具体过程的解释,懒得看可以直接看获取思路 有关this的取值请移步JavaScript笔记--this的取值 获取this的过程 Runtim ...

  2. (转载)OC学习篇之---类的定义

    之前已经介绍了OC中的一个程序HelloWorld,今天我们继续学习OC中类的相关知识. OC和C的最大区别就是具有了面向对象的功能,那么说到面向对象,就不得不说类这个概念了,如果学过Java的话,那 ...

  3. OC学习篇之---类的定义

    OC中类的相关知识 OC和C的最大区别就是具有了面向对象的功能,那么说到面向对象,就不得不说类这个概念了,如果学过Java的话,那么对类和对象的概念就不陌生了,因为Java是非常纯正的面向对象设计语言 ...

  4. Java类的定义与类的实例化

    目录 Java类的定义与类的实例化 类的定义 定义一个简单的类 定义一个成员变量 定义一个方法 定义一个构造器 类的实例化 创建对象及使用对象: 创建对象的过程在内存中的表现 Java类的定义与类的实 ...

  5. 【转载】 C++多继承中重写不同基类中相同原型的虚函数

    本篇随笔为转载,原文地址:C++多继承中重写不同基类中相同原型的虚函数. 在C++多继承体系当中,在派生类中可以重写不同基类中的虚函数.下面就是一个例子: class CBaseA { public: ...

  6. C#正则表达式编程(一):C#中有关正则的类

    正则表达式是一门灵活性非常强的语言,匹配同样的字符串可能在不同的开发人员那里会得到不同的结果,在平常的时候也是用的时候看看相关资料,不用的时候就丢在脑后了,尽管在处理大部分情况下都能迅速处理,但是处理 ...

  7. 《JS权威指南学习总结--9.3 JS中JAVA式的类继承》

    内容要点: 一.JS中的类 1.JAVA或其他类似强类型 面向对象语言的 类成员的模样 实例字段:它们是基于实例的属性或变量,用以保存独立对象的状态. 实例方法: 它们是类的所有实例所共享的方法,由每 ...

  8. Java中Date和Calender类的使用方法

    查看文章     Java中Date和Calender类的使用方法 2009-10-04 20:49 Date和Calendar是Java类库里提供对时间进行处理的类,由于日期在商业逻辑的应用中占据着 ...

  9. Filter组件开发中的SDK基类分析

    DirectShow SDK提供了一套开发Filter的基类源代码.基于这些基类开发Filter将大大简化开发过程. 1.CBaseObject 大部分SDK类都从CBaseObject类(参见com ...

随机推荐

  1. windows 安装 .net core 环境

    windows 安装 环境说明 window10系统 .net core 1.0.1 visual studio code 安装 .net core Windows系统下安装软件基本上属于傻瓜式安装, ...

  2. java入门---简介&简单输出小例子&开发前准备

        Java是由Sun Microsystems公司于1995年5月推出的Java面向对象程序设计语言和Java平台的总称.由James Gosling和同事们共同研发,并在1995年正式推出.J ...

  3. PHP.39-扩展-锁机制解决并发-MySQL锁、PHP文件锁

    锁机制适用于高并发场景:高并发订单.秒杀…… apache压力测试 Mysql锁详解 语法 加锁:LOCK TABLE 表名1 READ|WRITE, 表名2 READ|WRITE ......... ...

  4. 新版IdFTP解决中文乱码问题

    用XE10后开发FTP客户端,发现有中文乱码问题.这里也主要是编码的问题,在connect链接后,需要设置编码方可. 注意:  IndyTextEncoding_OSDefault;   该代码可能需 ...

  5. Java 基础------16进制转2进制

    我们知道,数字8用二进制表示为:1000 用16进制表示为:8 那么我给你一个16进制的数字,0x7f,他的二进制是什么呢? 一个16进制的位数,用4位表示.比如,0x 7 f 其中: 7用4位二进制 ...

  6. jdk带的一些工具,强悍

    这些工具有的已经接触到了,功能很强悍,但是使用也有点复杂(参数) 在代码中使用System.setProperty()或者在启动程序时使用-D选项设置代理服务器地址和端口 看看别人的研究: JDK自带 ...

  7. jmeter多用户登录跨线程组操作传值

    项目需求: 需要登录两个用户A.B,用户A操作完后会通知B,然后B再操作,B操作完结束或者再通知A. 实现思路: 1. 设置两个线程组Ⅰ.Ⅱ,组Ⅰ添加cookie管理器,里面添加用户A的操作:组Ⅱ添加 ...

  8. linux中wget 、apt-get、yum rpm区别

    wget 类似于迅雷,是一种下载工具, 通过HTTP.HTTPS.FTP三个最常见的TCP/IP协议下载,并可以使用HTTP代理名字是World Wide Web”与“get”的结合. yum: 是r ...

  9. Windows下nginx作为静态资源服务器使用

    一.Nginx下载与安装 1.nginx官方下载地址:http://nginx.org/ 2.下载完后将压缩包解压即可 3.nginx配置文件为根目录下conf\nginx.conf 二.Nginx常 ...

  10. you need to resolve your current index first 已解决

    从一个分支A切换到另一个分支B后,对切换后的B分支进行pull操作,因为pull操作实际上包含了fetch+merge操作,在执行 merge操作时,由于很长时间没有对B分支执行过pull/merge ...