目录

  • 前言
  • 一、原型链继承
  • 原型链继承的概念
  • 原型链继承的不足
  • 二、借用构造函数继承
  • 借用构造函数继承的概念
  • 利用构造函数继承的不足
  • 三、组合继承(原型链继承+构造函数继承)
  • 组合继承的概念
  • 组合继承的缺陷
  • 四、寄生组合性继承
  • 寄生组合性继承的概念
  • 寄生组合性继承的优点
  • 多重继承
  • 多重继承的概念
  • 总结


前言

在JS中既然没有类,那继承也是没有的,但面对家财万贯的家产时,我们又岂能无动于衷呢?于是我们想到了用模拟的方式,实现继承。

一、原型链继承

原型链继承的概念

在前一篇文章(javaScript(ES5)面向对象——原型模式实现原理),我们已经分析过了,在JS中每一个对象都存在原型对象,且可以通过原型对象共享属性和方法。原型链继承就是通过改变子类的原型对象为父类的原型对象,从而实现继承。

见图所示:

es5 模块_es5 模块

实现代码:

function Farther(){
    this.name = ['hello'];
    this.money = 999999;
}
Farther.prototype.say = function(){
    console.log(this.name);
}

function Son(){

}

// 原型链继承
Son.prototype = new Farther();
Son.prototype.constructor = Son;
var xdl = new Son();
console.log(xdl.money);   // 999999
xdl.say();     // ["hello"]

原型链继承的不足

原型链继承一共有以下两个问题:

  1. 原型链继承中,子类的实例对象会共享父类的实例对象属性,当父类的实例对象属性是引用数据类型时,一旦子类中的实例对象对其修改,则子类所有的实例对象所继承的相应属性值都会发现改变
var xdl = new Son('xdl');
var xdl2 = new Son('xdl2');
xdl.name.push('world!');
console.log(xdl.name);   // ["hello", "world!"]
console.log(xdl2.name);  // ["hello", "world!"]
  1. 当创建子类的实例对象时,无法传参给父类,也就无法使用父类的构造函数

二、借用构造函数继承

借用构造函数继承的概念

为了解决使用原型链继承无法在创建子类实例对象时,无法传参给父类的缺陷,因此我们使用call(apply、bind)的方式来调用父类的构造函数,从而实现子类向父类传参的效果。

实现代码:

function Father(name){
    this.name = name;
    this.sleep = function(){
        console.log('正在睡觉');
    }
}
Father.prototype.money = 999999;
Father.prototype.say = function(){
    console.log(this.name);
}

function Son(name){
    Father.call(this, name);
}

var xdl = new Son('xdl');
console.log(xdl.name);  // xdl
console.log(xdl.money);  // undefined
console.log(xdl);

打印结果:

es5 模块_es5 模块_02

利用构造函数继承的不足

我们在打印结果中可以看出,子类只继承了父类的实例对象属性和方法,但父类的原型对象并没有被继承下来。也就是说,父亲的家产没有被继承下来啊,那怎么行!

三、组合继承(原型链继承+构造函数继承)

组合继承的概念

原型链继承和构造函数继承,都各有各的优点与缺点,都说鱼和熊掌不可兼得,但为了继承到完整的家产,我们必须要拼命想办法继承。

es5 模块_javascript_03


实现代码:

function Father(name){
    this.name = name;
    console.log('我被调用了');
}
Father.prototype.money = 999999;

function Son(name){
    Father.call(this, name);
}

Son.prototype = new Father();  // 父类的创建的对象当作子类的原型对象
Son.prototype.constructor = Son;
var xdl = new Son('xdl');
console.log(xdl);
console.log(xdl.money);

运行结果:

es5 模块_es5 模块_04

组合继承的缺陷

分析结果我们可以得出,父类的构造函数会被调用两次:一次是实例化子类属性和方法,第二次是继承父类的共享属性和方法

四、寄生组合性继承

寄生组合性继承的概念

在组合继承中无非就是调用父类构造函数的次数过多,那我们就想办法减少调用的次数。那我们应该从哪里入手?
我们来分析一下:一共有两个地方调用父类的构造函数,第一个地方:Father.call(this, name);,这个地方是通过call的方式,调用父类构造函数,从而继承父类实例对象的属性和方法;第二个地方Son.prototype = new Father();,这个地方是将父类的实例对象当作子类的原型对象,继承父类的共享属性和方法。第一个地方无法优化,但第二个地方,不就是要得到父类的原型对象吗?那我们直接把父类的原型对象取出来,再用Object.create()以父类的原型对象为基础创建一个对象,最后作为子类的原型对象。
实现代码:

function Father(name){
    this.name = name;
}
Father.prototype.money = 999999;

function Son(name){
    Father.call(this, name);
}

Son.prototype = Object.create(Father.prototype);  // 优化的地方
Son.prototype.constructor = Son;
var xdl = new Son('xdl');
console.log(xdl);    
console.log(xdl.money);

运行结果:

es5 模块_构造函数_05

寄生组合性继承的优点

寄生组合性继承中,创建子类的实例对象时,可以向父类传参继承父类实例对象的属性和方法,同时还可以继承父类的原型对象,得到共享的属性和方法,并且不会像组合性继承那样调用两次父类构造函数,成功继承父亲全部的家产!

es5 模块_javascript_06

多重继承

多重继承的概念

在前面,我们成功继承了父亲的全部家产,但是母亲的家产呢?母亲给予我们更多的是暖暖的母爱,所以我们必须要得到这无价的爱。这就涉及到了多重继承的问题,在JS中通过混入技术(把另外一个对象拷贝到当前原型链上),把父类的原型对象拷贝到当前的原型链上,从而实现多重继承。

实现代码:

function Animal(name){
    this.name = name;
}

function Father(){
    this.name = '父亲';
}
Father.prototype.money = 999999;

function Mother(){
    this.name = '母亲'
}
Mother.prototype.love = function(){
    console.log('母亲的关爱');
}

function Son(name){
    Animal.call(this, name);
}

Son.prototype = Object.create(Father.prototype);
Object.assign(Son.prototype, Mother.prototype);
Son.prototype.constructor = Son;
var xdl = new Son('xdl');
console.log(xdl);
console.log(xdl.money);
xdl.love();

运行结果:

es5 模块_javascript_07

总结

谁说鱼和熊掌不能兼得?成功通过足智多谋,继承到了父母的家产,父母再也不用担心家产没有人继承了!