您的位置:晶晶的博客>前端>JavaScript的继承机制

JavaScript的继承机制

JavaScript中的继承与其他语言中基于类的继承有所差异,但又有异曲同工之妙,典型的C、Java乃至PHP中的继承是基于基类(或者说父类),涉及到类(class)的概念,子类继承基类,实例化子类后的对象即可使用继承自基类的方法(method)和属性( property),继承有关的理论中又有单继承、多继承以及覆盖(又称:重写 ,英文override)、重载(overload)、多态(polymorphism)的概念,学院派哪套就不多吧唧了~

从传统意义上来说,JavaScript并不真正具有类;除了不存在类,在ECMA-262标准中根本没有出现“类”这个词。JavaScript标准中倒是规定了“Object定义”有关的内容,而这里的“Object定义”逻辑上等价于其他程序设计语言中的类,所以JavaScript中的继承实质来讲就是:多个Object对象定义中实现其中某一个定义的对象Object在实例化后的对象内的属性和方法可从其他一个或多个定义的Object中继承的过程;更白话一些来说就是一个Object对象从另外一个或多个Object对象获得属性、方法的过程。在ECMA-262标准中JavaScript中的继承机制也不是明确规定的,所以JavaScript中的继承也是一种模仿实现,是那么个意思就够了,无需牵强附会必须要用C、Java那一套来理论。“Object定义”即“对象定义”是定义一个Object对象的过程,这Object先被定义,后被实例化才能生成实实在在的对象。

this关键字

在 ECMAScript 中,要掌握的最重要的概念之一是关键字this的用法,它用在对象的方法中。关键字this总是指向调用该方法的对象。

// this关键字
var oCar   = new Object;
oCar.color = "red";
oCar.showColor = function() {
  console.log(this.color);
};
oCar.showColor();

利用this关键字的这种总是指向调用该方法的对象的特性可以实现一个或多个对象的继承。

对象冒充

this关键字总是指向调用该方法的对象的特性,可以在多个对象定义中悄然改变this指向从而达到一个或多个对象中的属性和方法被另外一个对象继承的目的。

传统方式

// ClassA 基类的构造函数
function ClassA(sColor) {
    this.color = sColor;
    this.sayColor = function () {
        console.log(this.color);
    };
}
// ClassB 子类的构造函数
function ClassB(sColor, sName) {
    // 为ClassB新增一个newMethod属性值,赋值ClassA构造函数指向的指针(引用)
    this.newMethod = ClassA;
    // 执行newMethod属性值指向的函数方法即ClassA--- ClassA作为常规函数来执行而不是作为构造函数
    // 此处不是用的new语法,而是作为一个普通函数执行,且是在this指定的对象下执行的
    // 那么newMethod内部的this值指向的对象就变成了ClassB所实例化的对象(而不是ClassA)
    // 这个时候newMethod方法执行完毕就相当于为这个实例化ClassB产生的对象定义了color属性和sayColor方法
    // 从而实现了ClassB继承ClassA的方法和属性的功能
    this.newMethod(sColor);
    // 显式删除ClassB的newMethod属性值,ClassB实例化后的对象就无法使用该属性值,从而避免一些问题
    delete this.newMethod;

    // 为ClassB定义新属性和新方法,没有什么好说的
    this.name = sName;
    this.sayName = function () {
        console.log(this.name);
    };
}
var objA = new ClassA("蓝色");
var objB = new ClassB("红色", "Jea杨");
objA.sayColor();    //输出 "蓝色"
objB.sayColor();    //输出 "红色"---ClassB中并没有显式的定义sayColor方法,继承而来
objB.sayName();     //输出 "Jea杨"

上述示例代码中,ClassB仅继承了ClassA,属于单继承;经过适当的改动也是可以实现多继承的。如果存在两个类 ClassX 和 ClassY,ClassZ 想继承这两个类,在ClassZ构造函数定义时如法炮制即可完成多继承,如下:

function ClassX()
{
    // code  ClassX定义省略
}


function ClassY()
{
    // code  ClassY定义省略
}

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

    this.newMethod = ClassY;
    this.newMethod();
    delete this.newMethod;
}

call方法可手动指定this指向的方式

基本原理依然是利用this关键词的一些特性,而体现在call方法上则是Function对象的call方法的第一个参数可以用来指定被执行函数执行时的this指针。call方法的调用形式是:fun.call(thisArg[, arg1[, arg2[, ...]]]),其中thisArg参数为fun函数执行时指定的this值,其他1个或多个可选参数则为fun函数执行时依次传递的实参。

// ClassA 基类的构造函数
function ClassA(sColor) {
    this.color = sColor;
    this.sayColor = function () {
        console.log(this.color);
    };
}
// ClassB子类
function ClassB(sColor, sName) {
    // 原先的老旧方式
    /*
    this.newMethod = ClassA;
    this.newMethod(color);
    delete this.newMethod;
    */

    // 利用call方法实现继承
    // 利用ClassA的call特性,将ClassB实例化后的对象也就是this指向了ClassA
    // 执行了ClassA也就为ClassB实例化后的对象定义了color属性和sayColor方法
    // 从而实现了ClassB继承ClassA的方法和属性的功能
    ClassA.call(this, sColor);

    this.name = sName;
    this.sayName = function () {
        console.log(this.name);
    };
}
var objA = new ClassA("蓝色");
var objB = new ClassB("红色", "Jea杨");
objA.sayColor();    //输出 "蓝色"
objB.sayColor();    //输出 "红色"---ClassB中并没有显式的定义sayColor方法,继承而来
objB.sayName();     //输出 "Jea杨"

需要注意的是:为子类添加属性和方法,需要在对象冒充之后,否则可能出现属性或方法被修改的可能性。

prototype原型继承

这种继承方式利用了Object的属性prototype指向了对象的原型的原理,简言之,prototype指向的对象的任何属性和方法都被传递给那个类的所有实例。原型链利用这种功能来实现继承机制。

// 构造函数ClassA,可以理解成“基类”
function ClassA() {

}

// 为“基类”的原型增加一个名为color的属性并赋值blue
ClassA.prototype.color = "blue";

// 为“基类”的原型增加一个名为sayColor的方法
ClassA.prototype.sayColor = function () {
    console.log(this.color);
};

// 构造函数ClassB
function ClassB() {

}

// 将ClassB的对象原型对象赋值为ClassA的实例实现ClasB继承ClassA的属性和方法的目的
ClassB.prototype = new ClassA();

var objB = new ClassB();
objB.sayColor(); // 输出 blue

注意:调用 ClassA 的构造函数赋值给ClassB的对象原型时没有传递参数。这在原型链中是标准做法,通行的规则是要确保原型链继承中“基类”构造函数没有任何参数。

原型链的不足之处是不支持多继承;关于原型链继承比较容易理解的方式为:“子类”原型链会用“基类”实例化对象重写“子类”的 prototype 属性。

对象冒充和原型链继承混合方式

对象冒充实现继承的主要问题是必须使用构造函数方式,这并不是什么问题,但却不是最好的选择;而使用原型链继承,又无法使用带参数的构造函数;所以一般最佳的继承方式是对象冒充和原型链继承混合使用。

// 构造函数ClassA,可以理解成“基类”
function ClassA(sColor) {
    this.color = sColor;
}
// 为构造函数ClassA的对象原型添加一个方法
ClassA.prototype.sayColor = function () {
    console.log(this.color);
};

// 构造函数ClassB,可以理解成“子类”
function ClassB(sColor, sName) {
    // 通过call方式实现对象冒充的继承-->子类获得了color属性
    ClassA.call(this, sColor);

    // 构造函数中新增一个name属性
    this.name = sName;
}

// 使用对象原型实现对象继承
ClassB.prototype = new ClassA();

// 子类对象原型上添加方法
ClassB.prototype.sayName = function () {
    console.log(this.name);
};

var objA = new ClassA("蓝色");
var objB = new ClassB("红色", "Jea杨");
objA.sayColor(); //输出 "蓝色"

// 执行继承至原型链的方法,方法体中输出了对象冒充方式继承的属性color
// (此例中子类示例化后的对象属性color又通过构造函数赋值)
objB.sayColor(); //输出 "红色"
objB.sayName(); //输出 "Jea杨"

对象仿冒是一种对象实例级别的继承,是基于对象实例;prototype继承利用对象原型,对象原型的改变会传播到所有其他该原型链下的对象上的特性实现继承,是基于对象原型。

转载请注明本文标题和链接:《JavaScript的继承机制

相关推荐

哟嚯,本文评论功能关闭啦~