javascript面向对象:继承、多态
继承
js中同样可以实现类的继承这一面向对象特性,继承父类中的所有成员(变量和属性),同时可扩展自己的成员,下面介绍几种js中实现继承的方式:
1,对象模仿:通过动态的改变 this 指针的指向,实现对父类成员的重复定义,如下:
- function ClassA(paramColor) {
- this.color = paramColor;
- this.sayColor = function() {
- alert(this.color);
- }
- }
- function ClassB(paramColor, name) {
- //冒充并实现classA中的成员
- this.newMethod = ClassA;
- this.newMethod(paramColor);
- //删除掉对ClassA类冒充所使用的函数对象。
- delete this.newMethod;
- this.name = name;
- this.sayName = function() {
- alert(this.name);
- }
- var ogj = new ClassB("yellow", "apple");
- console.log("实例obj是否是ClassA的对象" + (obj instanceof ClassA));
- console.log("实例obj是否是ClassB的对象" + (obj instanceof ClassB));
- }
上例中我们实现了两个类,ClassA和ClassB,在ClassB的实现过程中,定义了一个函数newMethod来引用ClassA的构造函数并执行,这样就等于执行了A的构造函数,只不过此时的this指针指向的是类ClassB,故ClassA构造函数的这个模仿执行过程其实是给ClassB定义了相同的成员,最后删除这个起桥梁性质的冒充函数,执行结果如下:
根据执行结果我们可以看出,子类ClassB定义的对象并不同属其父类的实例,这种方式实现的继承并不是实际意义上的继承, 此外,这种方式只能模仿实现父类构造函数中定义的成员,对于父类中通过prototype定义的成员将不能继承。
2. 利用apply和call方法实现继承:同第一种方式相似,这种方式是通过apply和call方法动态改变this指针的引用实现对父类成员的重复定义,下面对ClassB改写如下:
- //call方法
- function ClassBEx(paramColor, name) {
- ClassA.call(this, paramColor);
- this.name = name;
- this.sayName = function() {
- alert(this,name);
- }
- }
- //aply方法
- function ClassBEEx(paramColor, name) {
- //如果类A的构造函数与类B的构造函数参数顺序完全相同时可用
- ClassA.apply(this, arguments);
- this.name = name;
- this.sayName = function() {
- alert(this.name);
- }
- }
这种方式同上一种的优缺点一样,并不是实际意义上的继承。
3. 共享prototype对象实现继承:子类通过对父类prototype对象进行共享以对父类成员的定义,从而实现继承,下面对ClassA和ClassB进行重新定义:
- //类ClassA的定义
- function ClassA(paramColor) {
- this.color = paramColor;
- }
- ClassA.prototype.sayColor = function() {
- console.log("执行ClassA中的成员函数sayColor:" + this.color);
- }
- //类ClassB的定义
- function ClassB(paramColor, name) {
- this.name = name;
- }
- //类ClassB共享使用类ClassA的prototype对象
- ClassB.prototype = ClassA.prototype;
- ClassB.prototype.sayName = function() {
- console.log(this.name);
- }
- //ClassB重写了类ClassA中的函数成员
- ClassB.prototype.sayColor = function() {
- console.log(this.color);
- }
- var objA = new ClassA("yellow");var obj = new ClassB("red","apple");
- console.log("实例obj的color属性" + obj.color);
- console.log("实例obj是否是ClassA的对象" + (obj instanceof ClassA));
- console.log("实例obj是否是ClassB的对象" + (obj instanceof ClassB));
- objA.sayColor();
上面阴影部分代码实现子类ClassB对父类ClassA的prototype对象进行共享,执行结果如下:
结果有点点意外,可以总结为以下几点:
1. 共享prototype对象可以实现子类的实例同属于父类的实例,这点可通过 instance of 返回为true看出;
2. 这种方式的继承只能继承父类prototype中的定义的父类成员,对于父类构造函数中的成员则不能继承,如上图:子类实例obj的color属性为undefined。
3. 共享原型(prototype)法,实际上是使父类和子类的都引用同一个prototype对象,js中除了基本数据类型(数值、字符串、布尔类等),所有的赋值都是引用传递,而不是值传递,上述的共享导致ClassA和ClassB的prototype对象始终保持一致,所以当子类ClassB重复定义了父类中的sayColor函数后,父类中的sayColor也同样更新了,故调用父类sayColor后输出的是“red”。
4. 共享原型方法会导致基类和派生类定义自己的成员时互相干扰。
总之,此方法还是不能实现实际意义上的继承。
4. 通过反射机制和prototype实现继承:在共享原型的基础上进行了改进,通过遍历基类的原型对象来给派生类原型对象赋值,以达到继承的目的,具体如下:
- //类ClassA的定义
- function ClassA(paramColor) {
- this.color = paramColor;
- }
- ClassA.prototype.sayColor = function() {
- console.log("执行ClassA中的成员函数sayColor:" + this.color);
- }
- //类ClassB的定义
- function ClassB(paramColor, name) {
- this.name = name;
- }
- //遍历基类的原型对象来给自己的原型赋值
- for (var p in ClassA.prototype) {
- ClassB.prototype[p] = ClassA.prototype[p];
- }
- ClassB.prototype.sayName = function() {
- console.log(this.name);
- }
- //ClassB重写了类ClassA中的函数成员
- ClassB.prototype.sayColor = function() {
- console.log("执行ClassB中的成员函数sayColor:red");
- }
- var objA = new ClassA("yellow");
- var obj = new ClassB("red", "apple");
- console.log("实例obj的color属性" + obj.color);
- console.log("实例obj是否是ClassA的对象" + (obj instanceof ClassA));
- console.log("实例obj是否是ClassB的对象" + (obj instanceof ClassB));
- objA.sayColor();
- obj.sayColor();
上面阴影部分的代码为遍历基类(ClassA)的prototype对象然后赋值给派生类ClassB的prototype对象,实现对基类的成员进行继承,执行结果如下:
由上图可见,基类和派生类的prototype是独立的,派生类继承了基类prototype定义的成员,并添加和重写了基类的成员函数sayColor,它们的执行结果互不干扰,唯一的缺憾是当前这种方式仍然不能继承基类构造函数中定义的成员,这一点可以通过在派生类的构造函数中添加一行代码实现,改写派生类ClassB的定义如下:
- //类ClassB的定义
- function ClassB(paramColor, name) {
- ClassA.call(this, paramColor);
- this.name = name;
- }
这样将基类的构造函数通过this指针附加到派生类的执行上下文中执行,实现对基类构造函数中定义的成员的继承。
为了提高代码的可读性,我们改进遍历基类prototype的实现过程:
- Function.prototype.inherit = function(superClass) {
- for (var p in superClass.prototype) {
- this.prototype[p] = superClass.prototype[p];
- }
- }
通过给Function对象添加成员方法,我们给所有的函数类型对象添加了一个静态方法,实现类的继承我们可以通过下面这句代码:
- ClassB.inherit(ClassA);
从继承的角度,上面这种方式更加容易被接受,但是有一点,通过反射(遍历)结合prototype实现继承的派生类,如果需要额外定义自己的成员,则只能通过对ptototype对象定义新的属性(ClassB.prototype.newAttr=?)来实现,而不能通过无类型方式(ClassB.prototype={}),否则会覆盖掉从基类继承下来的成员。
5. 继承的优化:主要对最后一种继承机制进行优化,定义一个Extend函数,实现对从基类继承后的对象的一个扩展,从而使得派生类添加新成员时更加高效,代码实现如下:
- /*
- * 将对象p中的属性全部添加到o对象中,如果存在重复,则直接覆盖
- */
- function extend(o, p) {
- for (prop in p) {
- o[prop] = p[prop];
- }
- return o;
- }
- /*
- * 创建以o对象为原型的新的对象。
- * 新的对象包含o中所有的成员
- */
- function inherit(o) {
- if (o == null) throw TypeError();
- if (Object.create) {
- return Object.create(o);
- }
- var t = typeof p;
- if (t !== "Object" && t !== "function") throw TypeError();
- function f() { }
- f.prototype = o;
- return new f();
- }
- /*
- * 通过Function给每个函数对象添加一个静态方法
- * constructor:派生类构造函数
- * methods:派生类需要新定义的成员方法
- * statics:派生类需要定义的静态变量或方法的集合
- * 返回派生类构造函数
- */
- Function.prototype.extend = function(constructor, methods, statics) {
- return definedSubClass(this, constructor, methods, statics);
- }
- /*
- * js类继承的核心方法
- * superClass:基类的构造函数(extend的执行时this指针,执行函数对象本身)
- * constructor:派生类构造函数
- * methods:派生类需要新定义的成员方法
- * statics:派生类需要定义的静态变量或方法的集合
- * 返回派生类构造函数
- */
- function definedSubClass(superClass, constructor, methods, statics) {
- constructor.prototype = inherit(superClass.prototype);
- constructor.prototype.constructor = constructor;
- if (methods) extend(constructor.prototype, methods);
- if (statics) extend(cosntructor, statics);
- return constructor;
- }
这些都是实现类继承模板的核心函数,主要是通过Function对象给所有的函数类型的对象添加了一个静态函数,有了上面的函数,实现上面ClassB继承ClassA,我们可以改为成:
- //类ClassA的定义
- function ClassA(paramColor) {
- this.color = paramColor;
- }
- ClassA.prototype.sayColor = function() {
- console.log("执行ClassA中的成员函数sayColor:" + this.color);
- }
- //ClassA作为基类派生出ClassB
- var ClassB = ClassA.extend(function(paramColor, name) {
- //构造函数(成员属性由构造函数定义)
- ClassA.call(this, paramColor);
- this.name = name;
- }, {
- //新定义或者重新定义的方法
- sayName: function() {
- console.log(this.name);
- },
- sayColor: function() {
- console.log("执行ClassB中的成员函数sayColor:red");
- }
- },
- {
- //无静态成员
- });
- var objA = new ClassA("yellow");
- var obj = new ClassB("red", "apple");
- console.log("实例obj的color属性" + obj.color);
- console.log("实例obj是否是ClassA的对象" + (obj instanceof ClassA));
- console.log("实例obj是否是ClassB的对象" + (obj instanceof ClassB));
- objA.sayColor();
- obj.sayColor();
阴影部分,我们通过扩展的extend函数实现了类的继承,简单明了,执行上面的例子,结果如下:
可以看出,优化后的方法完美的实现了js类的继承中遇到的几个问题。
多态
面向对象编程中的多态主要是通过抽象类和抽象函数实现的,js中也可以从这两个方面实现多态。传统意义上的多态,是通过派生类继承并实现基类中的抽象(虚)函数来实现的,含有抽象函数的类是抽象类,抽象类是不能够实例化的,同时,抽象函数没有函数体,也不能够直接调用,只能有派生类继承并实现。在高级程序语言中,上述这些检测均在程序编译时进行,不符合要求的程序编译将不通过,但是在js中,有了些许变化:
1. js是解释性语言,不需要进行预编译,所以js中抽象类和抽象函数的使用并没有那么严格的要求。
2. js中可以对未定义的方法进行调用,当然这一过程会报错,而检测时在执行调用时进行的。
所以,js中的抽象类可以定义实例,但就其意义而言,我们可以定义一个空的没有成员的类来代替,同样,js中的抽象函数,我们可以不必在基类中声明,直接进行调用,在派生类中实现即可,当然,也可以通过在基类中定义一个空的抽象方法实现,代码如下:
- function ClassA() {
- //抽象类,类的实现过程为空
- }
- ClassA.prototype = {
- sayColor: function() {
- //直接调用抽象方法
- this.initial();
- },
- //定义一个空的抽象方法由派生类去实现,也可以不定义
- initial: function() { }
- }
- //ClassA作为基类派生出ClassB
- var ClassB = ClassA.extend(function(name) {
- this.name = name;
- }, {
- //实现基类中的抽象方法
- initial: function() {
- console.log(this.name);
- }
- },
- {
- //无静态成员
- });
这样的实现与真正意义上的多态相差有点大,可能会让人疑惑这种必要性,为了最大程度的满足严格意义上的多态,我们改写上面的代码如下:
- //抽象类
- function ClassA() { throw new Error("can't instantiate abstract classes."); }
- ClassA.prototype = {
- initial: function() { throw new Error("can't call abstract methods."); }
- }
- //ClassA作为基类派生出ClassB
- var ClassB = ClassA.extend(function(name) {
- this.name = name;
- }, {
- //实现基类中的抽象方法
- initial: function() {
- console.log(this.name);
- }
- },
- {
- //无静态成员
- });
为了不让抽象类实例化,我们直接在其构造函数中抛出异常,为了不能直接调用抽象方法,我们也直接在其抽象方法中抛出异常,这样我们就满足了抽象类/方法的严格要求。
javascript面向对象:继承、多态的更多相关文章
- JavaScript面向对象—继承的实现
JavaScript面向对象-继承的实现 前言 面向对象的三大特性:封装.继承和多态.上一篇我们简单的了解了封装的过程,也就是把对象的属性和方法封装到一个函数中,这一篇讲一下JavaScript中继承 ...
- javaScript面向对象继承方法经典实现
转自原文javaScript面向对象继承方法经典实现 JavaScript的出现已经将近20多年了,但是对这个预言的褒贬还是众说纷纭.很多人都说JavaScript不能算是面向对象的变成语言.但是Ja ...
- JavaScript面向对象继承方法
JavaScript的出现已经将近20多年了,但是对这个预言的褒贬还是众说纷纭.很多人都说JavaScript不能算是面向对象的变成语言.但是JavaScript的类型非常松散,也没有编译器.这样一来 ...
- JavaScript 面向对象继承详解
题记 由于js不像java那样是完全面向对象的语言,js是基于对象的,它没有类的概念.所以,要想实现继承,一般都是基于原型链的方式: 一.继承初探 大多数JavaScript的实现用 __proto_ ...
- day25 面向对象继承,多态,
这两天所学的都是面向对象,后面还有几天也是它,面向对象主要有三个大的模块,封装,继承,多态,(组合),昨天主要讲了面向对象的命名空间,还有组合的用法,今天是讲的继承还有继承里面所包括的钻石继承,以及多 ...
- Javascript 面向对象-继承
JavaScript虽然不是面向对象的语言,但是我们通过构造可以让其支持面向对象,从而实现继承.重写等面向对象的特性.具体代码如下: //创建类Person function Person(age,n ...
- javascript面向对象——继承
javascript和其他语言相比,它没有真正意义上的继承,也不能从一个父类extends,要实现它的继承可以通过其他方式来实现: 步骤:1.继承父类的属性 2.继承父类的原型 下面就以一个拖拽为例子 ...
- JavaScript面向对象--继承 (超简单易懂,小白专属)
一.继承的概念 子类共享父类的数据和方法的行为,就叫继承. 二.E55如何实现继承?探索JavaScript继承的本质 2.1构造函数之间的"复制粘贴" 第一条路是通过构造函数来继 ...
- 07JAVA基础面向对象-继承/多态
一.继承 1.概念 子类的共性 重用现有类并在此基础上进行扩展 public class 子类 extends 父类{} 2.继承中的成员访问 成员变量 成员方法 局部->本类中成员变量-> ...
- JavaScript 面向对象继承的实现
<script type="text/javascript"> function Animal () { this.species="Animal" ...
随机推荐
- Azure HDInsight 和 Spark 大数据实战(二)
HDInsight cluster on Linux 登录 Azure portal (https://manage.windowsazure.com ) 点击左下角的 NEW 按钮,然后点击 DAT ...
- 贪吃蛇(Java实现)
程序效果: 代码: //Snake.java 1 package snake; import javax.swing.*; import java.awt.*; import java.awt.eve ...
- [tem]高精度1
1 //倒着存 B取的低精最大值所以简化了一点 #include <iostream> #include <cstdio> #include <cstring> # ...
- java1.8函数式接口
package com.wzy.t1; @FunctionalInterface//此注解用来声明此接口为函数式接口 public interface People { /** * 1.函数式接口只能 ...
- TCP/IP协议中网关和子网掩码概念
网关: 不同网段的IP是不能直接互通的,需要一个设备来转发,这个设备就是网关,一般就是路由器,那么路由器的地址就是网关地址. 比如192.168.2.31要往192.168.3.31发送一条消息,他们 ...
- Nginx负载均衡实践之一:基本实现
由于现在的网站架构越来越大,基于互联网的用户也是日渐增长,所以传统的单机版服务器已经渐渐不能适应时代发展的需要.最近在和其他企业接触的过程中,发现对于互联网的经验尤为看重,所谓的互联网经验,其实就是指 ...
- 20145208 GDB调试汇编堆栈过程分析
20145208 GDB调试汇编堆栈过程分析 测试代码 #include<stdio.h> short addend1 = 1; static int addend2 = 2; const ...
- 【原】Learning Spark (Python版) 学习笔记(二)----键值对、数据读取与保存、共享特性
本来应该上周更新的,结果碰上五一,懒癌发作,就推迟了 = =.以后还是要按时完成任务.废话不多说,第四章-第六章主要讲了三个内容:键值对.数据读取与保存与Spark的两个共享特性(累加器和广播变量). ...
- 86 ipmitools-查看硬件信息工具
1.简介 IPMI(Intelligent Platform Management Interface)即智能平台管理接口是使硬件管理具备"智能化"的新一代通用接口标准.用户可以利 ...
- PRINCE2七大原则
我们先来回顾一下,PRINCE2七大原则分别是持续的业务验证,经验学习,角色与责任,按阶段管理,例外管理,关注产品,剪裁. 第三个原则:明确定义的角色和职责. 项目离不开人员,错误的人来了,合适的人没 ...