首先,我们用一个经典例子来简单阐述一下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) |
04 |
this .sayColor = function () { |
08 |
function ClassB(sColor, sName) |
10 |
this .newMethod = ClassA; //为ClassB添加新方法newMethod,该方法引用ClassA |
11 |
this .newMethod(sColor); |
12 |
delete this .newMethod; //为了避免影响ClassA的属性和方法,将该方法销毁。因为函数ClassA只是引用值 |
15 |
this .sayName = function () |
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 想继承这两个类,可以使用下面的代码:
2 |
this .newMethod = ClassX; |
5 |
this .newMethod = ClassY; |
由于这种继承方法的流行,ECMAScript 的第三版为 Function 对象加入了两个方法,即 call() 和 apply()。
call() 方法
call() 方法是与经典的对象冒充方法最相似的方法。它的第一个参数用作 this 的对象。其他参数都直接传递给函数自身。例如:
1 |
function sayColor(sPrefix,sSuffix) |
3 |
alert(sPrefix + this .color + sSuffix); |
6 |
var obj = new Object(); |
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); |
7 |
this .sayName = function () { |
这里,我们需要让 ClassA 中的关键字 this 等于新创建的 ClassB 对象,因此 this 是第一个参数。第二个参数 sColor 对两个类来说都是唯一的参数。
apply()方法
apply() 方法有两个参数,用作 this 的对象和要传递给函数的参数的数组。例如:
1 |
function sayColor(sPrefix,sSuffix) { |
2 |
alert(sPrefix + this .color + sSuffix); |
4 |
var obj = new Object(); |
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)); |
5 |
this .sayName = function () { |
同样的,第一个参数仍是 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); |
5 |
this .sayName = function () { |
当然,只有超类中的参数顺序与子类中的参数顺序完全一致时才可以传递参数对象。如果不是,就必须创建一个单独的数组,按照正确的顺序放置参数。此外,还可使用 call() 方法。
原型链
继承这种形式在 ECMAScript 中原本是用于原型链的。prototype 对象是个模板,要实例化的对象都以这个模板为基础。总而言之,prototype 对象的任何属性和方法都被传递给那个类的所有实例。原型链利用这种功能来实现继承机制。
如果用原型方式重定义前面例子中的类,它们将变为下列形式:
01 |
function ClassA(sColor) { |
05 |
ClassA.prototype.sayColor = function () { |
09 |
function ClassB(sColor, sName) { |
10 |
ClassA.call( this , sColor); |
14 |
//ClassB.prototype = ClassA.prototype; |
15 |
ClassB.prototype = new ClassA(); //调用 ClassA 的构造函数,没有给它传递参数。这在原型链中是标准做法。要确保构造函数没有任何参数 |
16 |
ClassB.prototype.sayName = function () { |
20 |
var objA = new ClassA( "blue" ); |
21 |
var objB = new ClassB( "red" , "John" ); |
在 ECMAScript 的弱类型世界中,这是极其有用的工具,不过使用对象冒充时不能使用它。原型链的弊端是不支持多重继承。记住,原型链会用另一类型的对象重写类的 prototype 属性。
混合方式
这种继承方式使用构造函数定义类,并非使用任何原型。对象冒充的主要问题是必须使用构造函数方式,这不是最好的选择。不过如果使用原型链,就无法使用带参数的构造函数了。开发者如何选择呢?答案很简单,两者都用。是用构造函数定义属性,用原型定义方法。这种方式同样适用于继承机制,用对象冒充继承构造函数的属性,用原型链继承 prototype 对象的方法。用这两种方式重写前面的例子,代码如下:
01 |
function ClassA(sColor) { |
04 |
ClassA.prototype.sayColor = function () { |
08 |
function ClassB(sColor, sName) { |
09 |
ClassA.call( this , sColor); // 在 ClassB 构造函数中,用对象冒充继承 ClassA 类的 sColor 属性 |
13 |
ClassB.prototype = new ClassA(); //用原型链继承 ClassA 类的方法 |
15 |
ClassB.prototype.sayName = function () { |
- ECMAScript中关于如何获取this的定义
文章中一些名词的翻译存疑,没有查过正式的中文名称 前面都是具体过程的解释,懒得看可以直接看获取思路 有关this的取值请移步JavaScript笔记--this的取值 获取this的过程 Runtim ...
- (转载)OC学习篇之---类的定义
之前已经介绍了OC中的一个程序HelloWorld,今天我们继续学习OC中类的相关知识. OC和C的最大区别就是具有了面向对象的功能,那么说到面向对象,就不得不说类这个概念了,如果学过Java的话,那 ...
- OC学习篇之---类的定义
OC中类的相关知识 OC和C的最大区别就是具有了面向对象的功能,那么说到面向对象,就不得不说类这个概念了,如果学过Java的话,那么对类和对象的概念就不陌生了,因为Java是非常纯正的面向对象设计语言 ...
- Java类的定义与类的实例化
目录 Java类的定义与类的实例化 类的定义 定义一个简单的类 定义一个成员变量 定义一个方法 定义一个构造器 类的实例化 创建对象及使用对象: 创建对象的过程在内存中的表现 Java类的定义与类的实 ...
- 【转载】 C++多继承中重写不同基类中相同原型的虚函数
本篇随笔为转载,原文地址:C++多继承中重写不同基类中相同原型的虚函数. 在C++多继承体系当中,在派生类中可以重写不同基类中的虚函数.下面就是一个例子: class CBaseA { public: ...
- C#正则表达式编程(一):C#中有关正则的类
正则表达式是一门灵活性非常强的语言,匹配同样的字符串可能在不同的开发人员那里会得到不同的结果,在平常的时候也是用的时候看看相关资料,不用的时候就丢在脑后了,尽管在处理大部分情况下都能迅速处理,但是处理 ...
- 《JS权威指南学习总结--9.3 JS中JAVA式的类继承》
内容要点: 一.JS中的类 1.JAVA或其他类似强类型 面向对象语言的 类成员的模样 实例字段:它们是基于实例的属性或变量,用以保存独立对象的状态. 实例方法: 它们是类的所有实例所共享的方法,由每 ...
- Java中Date和Calender类的使用方法
查看文章 Java中Date和Calender类的使用方法 2009-10-04 20:49 Date和Calendar是Java类库里提供对时间进行处理的类,由于日期在商业逻辑的应用中占据着 ...
- Filter组件开发中的SDK基类分析
DirectShow SDK提供了一套开发Filter的基类源代码.基于这些基类开发Filter将大大简化开发过程. 1.CBaseObject 大部分SDK类都从CBaseObject类(参见com ...
随机推荐
- 一张表搞清楚 php 的 is_null、empty、isset的区别
isset 判断变量是否已存在 empty 判断变量是否为空或为0 is_null 判断变量是否为NULL 变量 empty is_null isset $a=”” true false true $ ...
- keil5 mdk使用ST-Link II下载出现cannot halt the core解决办法
在正常的程序里面,我添加了MB85RS16(spi flash)这个外设驱动代码后,使用ST-Link II下载就出现cannot halt the core. 这个现象之前出现过1次,但是解决办法忘 ...
- Leecode刷题之旅-C语言/python-21.合并两个有序链表
/* * @lc app=leetcode.cn id=21 lang=c * * [21] 合并两个有序链表 * * https://leetcode-cn.com/problems/merge-t ...
- 对bluebird的理解
前言 Promise:把原来的回调写法分离出来,在异步操作执行完后,用链式调用的方式执行回调函数. 在公众号的开发里面用的const Promise = require('bluebird');con ...
- ABAP CDS ON HANA-(7)CDSビューでの集約
Aggregate expression in CDS View An aggregate expression calculates a single value from an operand o ...
- Git中从远程的分支获取最新的版本到本地——两种命令
Git中从远程的分支获取最新的版本到本地有这样2个命令: 1. git fetch:相当于是从远程获取最新版本到本地,不会自动merge Git fetch origin master git log ...
- 代码混淆 iOS
该方法只能针对有.m.h的类进行混淆,静态库等只有.h文件的没法进行混淆 代码混淆,刚刚看到是不是有点懵逼,反正我是最近才接触到这么个东西,因为之前对于代码和APP,只需要实现功能就好了,根本没有考虑 ...
- SQL的鸡肋:“视图”
不知道当年SQL定义者们设计视图时是出于什么样的考虑.实际效果是,视图夹在SQL指令和表之间,形成了一个三明治的结构.在这种结构下做检索,SQL指令每次都要通过视图转换,才能作用到表上.如果不 ...
- 九度OJ--Q1166
import java.text.DecimalFormat;import java.util.Scanner; /* * 题目描述: * 立方根的逼近迭代方程是 y(n+1) = y(n)*2/3 ...
- Tensorflow Estimators
这篇文章介绍tf.estimator,一个高级TensorFlow API,可以极大简化机器学习编程.Estimators封装了下面几个活动. 训练 评估 预测 出口服务(export for ser ...