javascript进阶知识20 - class的继承

82 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第12天,点击查看活动详情

我们知道构造函数是有继承的,而class的实质其实就是构造函数。但是class又是怎样实现继承的呢?

extends

class使用关键字extends实现的继承。

class Father {
    constructor(name) {
        this.name = name;
    }
    sayName() {
        console.log('Father:'+this.name)
    }
}

class Son extends Father {}

let ly = new Son('ly');
ly.sayName();   //  Father: ly

我们可以发现我们并没有在Son类中创建sayName函数,但是,我们使用extends继承Father后,我们却可以使用sayName函数。

image.png

我们可以发现,ly.__ proto __ === Son.prototype, 而Son.prototype.__ proto __ === Father.prototype. 以此形成了一条原型链,即Son原型继承了Father。当我们在ly实例中使用sayName方法时,会首先在ly实例上查找有没有这个方法,没有则去ly.__ proto__ 即Son的原型上查找,如果还没有,就会继续沿着原型链,去ly.__ proto__.__ proto__即Son.prototype.__ proto__ 即Father的原型上查找,发现有这个方法,就调用这个方法。

所以在类中使用extends的本质其实等于:

function Father(name) {
    this.name = name;
}
Father.prototype.sayName = function() {
     console.log('Father:'+this.name)
}

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

let ly = new Son('ly');
ly.sayName();  // Father:ly

其实就是上一节我们总结的:原型链+盗用构造函数 == 继承

super

我们还可以看出,在Son中并没有使用类似于盗用构造函数的方法,也就是没有Father.call(this,name),那么为何还会给实例绑定上name呢?

这就不得不说一下在类中有一个关键字super了。

super关键字只能在extends中使用,也就是说,如果我们在Son中没有extends Father,那么我们使用super就会报错。

class Father {
    constructor() {
        super()
    }
}
new Father() // Uncaught SyntaxError: 'super' keyword unexpected here

那么super的作用是什么呢?

在静态方法中,可以通过super调用继承的类上定义的方法:

class Father {
    static sayHi() {
        console.log('Hello')
    }
}

class Son extends Father {
    static sayHi() {
        super.sayHi()
    }
}

Son.sayHi(); // Hello

在constructor中使用super会调用父类构造函数,并将返回的实例赋值给this

class Father {}

class Son extends Father {
    constructor() {
        super();
        console.log(this instanceof Father)
    }
}

new Son();  // true

super()的行为如同调用构造函数,如果需要给父类构造函数传参,需要我们手动传参。

class Father {
    constructor(name) {
        this.name = name;
    } 
}

class Son extends Father {
    constructor(name) {
        super(name)
    }
}

console.log(new Son('ly'));  // Son {name: 'ly'}

如果我们在派生类(也就是子类)没有定义类构造函数,在实例化时会调用super(),而且会把传入的参数所有传给父类。

class Father {
    constructor(name) {
        this.name = name;
    } 
}

class Son extends Father {}

console.log(new Son('ly'));  // Son {name: 'ly'}

在子类中,不能在调用super之前引用this,否则会报错

class Father {
    constructor(name) {
        this.name = name;
    }
}

class Son extends Father {
    constructor(name,age) {
        this.age = age;
        super(name)
    }
}

console.log(new Son('ly',18));  
// Uncaught ReferenceError: 
// Must call super constructor in derived class before accessing 'this' or returning from derived constructor

在子类中显示的定义了constructor,则要么必须在其中调用super(),要么必须返回一个对象

class Father {}

class Son extends Father {
    constructor() {
    }
}

console.log(new Son()); 
// Uncaught ReferenceError: 
// Must call super constructor in derived class before accessing 'this' or returning from derived constructor

或者:但是不推荐这个,没有啥意义,都返回一个{}了,那么作为构造函数还有啥意义???

class Father {}

class Son extends Father {
    constructor() {
        return {}
    }
}

console.log(new Son()); // {}

所以这也就解决了为什么我们最开始在Son中没有调用父类构造函数,为啥ly实例可以有name属性。这就是以为我们在Son中没有显示定义constructor,那么js底层默认在new Son('ly')的时候就会在Son中执行super('ly')了。而在子类构造函数中使用super()就是就是等于在构造函数中使用Father.call(this),区别就是super会自动改变父类的this指向,让其指向子类。

此外,class还可以继承JS的内置类或者内置的构造函数(保持向下兼容),可以方便的扩展内置类型。