实现继承的几种方式

继承

相信我们在学习JS的过程中,都遇到了继承这个大boss,那我们今天就来挑战一下它这个副本。

ES5中的继承

原型链式继承

我们之前谈到了原型,原型链,那么原型链可以实现继承吗?

答案当然是可以的啊,步骤如下:

  1. 先定义一个构造函数,然后定义另一个构造函数
  2. 在后者构造函数的原型对象上添加前者的实例
  3. 创建后者的实例

这样新的实例就拥有了两个构造函数的属性。

function One(){
    this.numOne=1;
}
function Two(){
    this.numTwo=2;
}
One.prototype.getNumOne()=function(){
    return this.numOne;
}
Two.prototype=new One();
Two.prototype.getNumTwo=function(){
    return this.numTwo;
}
const two=new Two();
two.getNumOne(); // 1

注意:

  1. 所有的引用类型都继承了Object , 这个继承是通过原型链实现的,Object的原型指向null,null是原型链的末端
  2. 在使用原型原型链创建对象时候,不能使用字面量创建原型方法,否则会重写原型链
  3. 给原型添加方法的代码一定要放在原型替换代码之后,否则替换不成功

问题

  1. 原型属性是包含引用类型的值的时候,会被所有的实例共享,所以一般属性都在构造函数中定义,不在原型链中定义
  2. 在不影响其他对象实例的情况下,是无法给父类的构造函数传参的

构造函数式继承

思想

我们在子类型构造函数的内部调用父类构造函数,然后通过使用callapply方法在新创建的对象上执行构造函数。

function One(num){
    this.numOne=num;
}
function Two(){
       One.call(this,3);
    this.numTwo=2;
}
const two=new Two();
console.log(two.numOne);//3

问题

使用构造函数实现继承的方法挺好,但是就有一个问题,方法在构造函数中定义的无法进行服用,因为在One中的原型定义的方法,在子类型中是不可见的,所有的类型只能使用构造函数模型。

组合式继承

组合式继承是结合原型链继承和构造函数继承的发挥二者之长的继承。

One.prototype.getNumOne=function(){
    return this.numOne;
}
function Two(num1,num2){
    //使用了One(),实例对象继承了One的两个属性,会屏蔽掉原型Two.prototype中的亮哥同名属性;
    One.call(this,num1);
    this.numTwo=num2;
}
Two.prototype=new One();
//改变了Two的原型对象,要恢复原型指向;
Two.prototype.constructor=Two;;
Two.prototype.getNumTwo=function(){
    return this.numTwo;
}
var two1=new Two(1,2);
console.log(two.numOne); //1
console.log(two.numTwo); //2

var two2=new Two(2,4);
console.log(two.numOne); //2
console.log(two.numTwo); //4

问题

这个方式实现继承避免了原型链和构造函数的缺陷,也是javascript中最为常用的继承模式,但是这个方法有个无法避免的问题:

在任何情况下都会调用两次父类型的构造函数——创建子类型原型时候以及子类构造函数内部。

原型式继承

基本思想是:在已经有一个对象的基础上,然后把对象传给object函数,再根据具体需求,修改得到的对象。

object函数如下:

function object(o){
    function f(){}
    f.proptotype=o;
    return new f();
}

这个函数的原理我们之前见到过一个Object属性上的方法与之相似,就是Object.create()

他们都是接收两个参数:一是用作新对象的原型的对象和一个为新对象额外属性的对象,二是可选项。

var person={
    name:'张三',
    course:['Math','English','History']
};
var person1=Object.create(person);
console.log(person1.name);

var person2=Object.create(person);
console.log(person2.name);

特点

  1. Object.create()有兼容性问题,IE9以下不支持;
  2. 适用于没有必要创建构造函数,只需要一个对象与另一个对象保持一致类似的情况
  3. 对象内引用类型的属性会始终保持一致

寄生式继承

思路

创建一个仅用于封装继承过程的函数,该函数在内部来增强对象,最后返回对象;

function createObject(obj){
    const clone=Object.create(obj);
    clone.sayhi=function(){
        return 'hi';
    }
    return clone;
}
const person={
    name:'张三'
}
const person1=createObject(person);
console.log(person1.sayHi()); //hi

问题

使用寄生式函数来对对象添加函数,无法复用函数。

组合寄生式继承

思路

使用寄生式继承来继承父类型的原型,然后将结果指定给子类型的原型。

function inheritPrototype(child,parent){
    const prototype=Object.create(parent.prototype);
    prototype.constructor=child;
    child.prototype=prototype;
}
function One(num){
    this.num1=num;
    this.numList=[1,2,3,4];
}
One.prototype.getNumOne=function(){
    return this.num1;
}
function Two(num1,num2){
    One.call(this,num1);
    this.numTwo=num2;
}
inheritPrototype(Two,One);
Two.prototype.getNumTwo=function(){
    return this.num2;
}
var two=new One(3,5);
two.numList.push(8); //[1,2,3,4,8]
console.log(two.getNumOne());//3
console.log(two.getNumTwo());//5

特点

组合寄生式继承,只调用了依次父类的构造函数,避免了在子类型对象prototype上创建不必要的属性,保持了原型链不变,是引用类型最理想的继承范式。

ES6中的继承

实现方式

Class通过extends关键字来实现继承。

class One{
    constructor(name,age){
        this.name=name;
        this.age=age;
        this.hobby='Coding'
    }
    getOneInfo(){
        return `姓名:${this.name},年龄:${this.age}`;
    }
}
class Two extends One{
    constructor(x,y,gender){
        super(x,y);
        this.gender=gender;
    }
    getTwoInfo(){
        return `${super.getOneInfo()},性别:${this.gemder},爱好:${this.hobby}`;
    }
}
const two=new Two('张三','18','boy');
two.getTwoInfo();

子类必须在constructor方法中调用super方法,否则新建实例会报错。因为只有super()方法才能调用父类实例,不调用super(),子类就没有自己的this对象。只有调用super之后,才可以使用this关键字,否则会报错。

super关键字

super关键字,既可以当做函数使用,也可以当做对象来使用。

意味着这个东西,它既有prototype属性,也有__proto__属性。

  1. super作为函数调用时,代表父类的构造函数。ES6要求,子类的构造函数必须执行一次super函数。
  2. super作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。

区别

ES5的继承,先是创建子类的实例对象this,然后将父类的属性和方法添加到子属性实例对象的this上。

ES6的继承,先创建父类的实例对象this,然后在用子类的构造函数修改this,实现继承。