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);
    }
},
{
    //无静态成员
});

为了不让抽象类实例化,我们直接在其构造函数中抛出异常,为了不能直接调用抽象方法,我们也直接在其抽象方法中抛出异常,这样我们就满足了抽象类/方法的严格要求。

时间: 2024-10-12 11:24:42

javascript面向对象:继承、多态的相关文章

javascript 面向对象继承详解

一.继承初探 大多数JavaScript的实现用 __proto__ 属性来表示一个对象的原型链. 我们可以简单的把prototype看做是一个模版,新创建的自定义对象都是这个模版(prototype)的一个拷贝 (实际上不是拷贝而是链接,只不过这种链接是不可见,新实例化的对象内部有一个看不见的__proto__指针,指向原型对象) 当查找一个对象的属性时,JavaScript 会向上遍历原型链,直到找到给定名称的属性为止.查找方式可以这样表示: function getProperty(obj

3、面向对象-继承-多态

1.继承 子类可以继承父类的一切,一个子类只能有一个父类,一个父类可以有多个子类 //父类 class Ren{ public $name; public $sex; public $yuyan; function Say()    { echo $this->name."正在讲话!"; } } //美国人的子类 class America extends Ren{ public $ziben; //子类对父类的方法重写 function Say()    {    parent

JavaScript 面向对象 ( 继承 )

提问: 现在有一个"动物"对象的构造函数. function Animal(){ this.species = "动物"; } 还有一个"猫"对象的构造函数. function Cat(name,color){ this.name = name; this.color = color; } 怎样才能使"猫"继承"动物"呢? 一. 构造函数绑定 先来看一段代码: function ClassA(sColor)

javascript面向对象——继承

一. 构造函数绑定 先来看下面两个函数的意义: function ClassA(sColor){ this.color=sColor; this.sayColor=function(){ alert(this.color); }; } function ClassB(sColor,sName){ this.newMethod=ClassA; this.newMethod(sColor); delete this.newMethod; this.name=sName; this.sayName=f

JavaScript 面向对象继承的实现

1 <script type="text/javascript"> 2 function Animal () { 3 this.species="Animal"; 4 } 5 function Cat(name,color){ 6 this.name=name; 7 this.color=color; 8 } 9 //Apply 将父类对象的构造函数赋值到子类上,Animal.apply(this,arguments); 10 function Cat(

JavaScript 面向对象程序设计(下)&mdash;&mdash;继承与多态 【转】

JavaScript 面向对象程序设计(下)--继承与多态 前面我们讨论了如何在 JavaScript 语言中实现对私有实例成员.公有实例成员.私有静态成员.公有静态成员和静态类的封装.这次我们来讨论一下面向对象程序设计中的另外两个要素:继承与多态. 1 又是几个基本概念 为什么要说又呢? 在讨论继承时,我们已经列出了一些基本概念了,那些概念是跟封装密切相关的概念,今天我们要讨论的基本概念,主要是跟继承与多态相关的,但是它们跟封装也有一些联系. 1.1 定义和赋值 变量定义是指用 var a;

JavaScript面向对象的继承应用

面向对象语言的三大特征:继承.封装.多态 <!DOCTYPE html> <html> <head> <title>Extend-OPP</title> </head> <script type="text/javascript"> function Person(name,sex){ this.name=name; this.sex=sex; } Person.prototype.showName =

[JavaScript] JavaScript 面向对象设计 (3) : 多态与界面篇

在前一篇中我们介绍了基础的 JavaScript 继承实践法,透过 Object.prototype 我们可以自由决定对象要继承自哪个对象,也可以扩充对象目前现有的属性和方法 (和 C# 的 Extension Method 有异曲同工之妙),在本篇中,我们要来介绍面向对象的另一个特性:多态 (Polymorphism). 在前一篇中我们介绍了基础的 JavaScript 继承实践法,透过 Object.prototype 我们可以自由决定对象要继承自哪个对象,也可以扩充对象目前现有的属性和方法

JavaScript面向对象之类的继承

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-

Javascript面向对象特性实现封装、继承、接口详细案例——进级高手篇

Javascript面向对象特性实现(封装.继承.接口) Javascript作为弱类型语言,和Java.php等服务端脚本语言相比,拥有极强的灵活性.对于小型的web需求,在编写javascript时,可以选择面向过程的方式编程,显得高效:但在实际工作中,遇到的项目需求和框架较大的情况下,选择面向对象的方式编程显得尤其重要,Javascript原生语法中没有提供表述面向对象语言特性的关键字和语法(如extends.implement).为了实现这些面向对象的特性,需要额外编写一些代码,如下.