JavaScript学习笔记--继承

210 阅读4分钟

ES5的继承

类式继承

function Parent(userName){
    this.userName=userName;
}
Parent.getUser.prototype=function(){
    console.log("父类:",this.userName);
}
function Son(userName){
    Son.prototype=new Parent(userName);
    //让子类原型指向父类的一个实例,从而实现子类能继承父类的实例属性+原型属性,但实现不了
}
let son=new Son('son');
son.getUser();  //出错,无此方法
console.log(son);
/*
Son {}
__proto__:
constructor: ƒ Son(userName)
__proto__: Object
*/

出错的原因:在子类的构造函数里改变子类原型,而此时实例已经创建,则它的__proto__仍指向老的原型对象,所以改变子类原型对象并不会对子类实例有所改变。

改造:将实例的__proto__重新指向 Son.prototype,则在构造函数里写上'this.__proto__= Son.prototype;' 但此时实例的constructor指向的是Parent,所以在改变实例的__proto__之前还需加上‘Son.prototype.constructor=Son;’

思考:为什么是子类原型指向父类的实例对象,而不是父类的原型对象呢?

这样就不是继承了,而是和父类一样。而且在子类原型创建的属性方法父类也会具有,违背了多态性的特点。

//改造后
function Son(userName){
    Son.prototype=new Parent(userName);
    Son.prototype.constructor=Son;
    this.__proto__= Son.prototype;
}

但这样的写法太麻烦了,需要不断的重写属性,缘由就是在构造函数里改变了原型对象,那如果在实例对象创建之前调用呢?

function Parent(userName){
    this.userName=userName;
}
Parent.getUser.prototype=function(){
    console.log("父类:",this.userName);
}
function Son(userName){
    this.userName=userName;
}
Son.prototype=new Parent(); //实例还没有创建,无法传入参数
Son.prototype.constructor=Son;

let son=new Son('son');

显而易见,这个方法无法在调用父类构造函数时就传递参数。

构造函数式继承

function Parent(userName){
    this.userName=userName;
}
Parent.prototype.getUser=function(){
    console.log("父类:",this.userName);
}
function Son(userName){
    Parent.call(this,userName);
    //相当于把父类构造函数当作普通函数调用,但将this绑定成子类实例,因此子类实例具有
    //父类的实例属性/方法,但不具有原型上的属性/方法
}
let son=new Son('son');
console.log(son.userName);
console.log(son.getUser);//undefined

组合式继承(前两种方法结合)

function Parent(userName){
    this.userName=userName;
}
Parent.prototype.getUser=function(){
    console.log("父类方法:",this.userName);
}
function Son(userName,age){
    Parent.call(this,userName); //第二次调用
    this.age=age;
}
Son.prototype=new Parent(); //第一次调用
Son.prototype.constructor=Son;

let son=new Son('son');
son.getUser(); //父类方法:son

实现了子类继承父类的所有属性和方法,子类在原型上添加属性和方法也不会影响父类原型

问题:这样是调用了两次父类构造函数,而且第一次调用其实没有必要,因为无法传递参数给实例属性赋值,而且第二次调用时的作用就是去获取父类的实例属性。如果父类构造函数代码很多时,则调用两次对性能也会有所影响。

改造:创建一个空函数来代替父类构造函数,那么该空函数的原型要指向父类原型对象,然后子类原型对象指向该空函数实例,则是下面的第四种继承

寄生式组合式继承

function inherit(parent) {
        function F() { }    //空函数
        F.prototype=parent.prototype;
        return new F();
    }
    function Parent(userName){
        this.userName=userName;
    }
    Parent.prototype.getUser=function(){
        console.log("父类方法:",this.userName);
    }
    function Son(userName,age){
        Parent.call(this,userName);
        this.age=age;
    }
    Son.prototype=inherit(Parent);
    Son.prototype.constructor=Son;
    Son.prototype.getAge=function(){
        console.log("子类的方法",this.age);
    }
    let son=new Son('son',10);  //必须在inherit函数调用之后创建子类实例
    son.getUser(); //父类方法:son
    son.getAge();  //子类的方法 10

inherit方法其实是实现Object.creat()方法,所以可以直接调用。如下代码:

function inherit(parent,child) {
    child.prototype=Object.create(parent.prototype);//返回一个实例,实例的__proto__指向父类原型
    child.prototype.constructor=child;
}
    function Parent(userName){
        this.userName=userName;
    }
    Parent.prototype.getUser=function(){
        console.log("父类方法:",this.userName);
    }
    function Son(userName,age){
        Parent.call(this,userName);
        this.age=age;
    }
    inherit(Parent,Son);
    //Son.prototype.constructor=Son;
    Son.prototype.getAge=function(){
        console.log("子类的方法",this.age);
    }
    let son=new Son('son',10);
    son.getUser(); //父类方法:son
    son.getAge();  //子类的方法 10

这样的写法可以让子类继承父类原型上的属性方法,也具有父类的实例属性和方法,不会使得不共享的属性出现在原型链中。而且在子类的原型添加属性和方法也不会影响父类。

注意:在inherit()函数调用之后,如果再往父类原型上添加属性方法,子类是无法继承到的。而且在子类的原型上添加属性方法只能在调用inherit()函数之后,否则会被覆盖。

ES6继承

使用关键字class定义类、extends实现子类继承父类、constructor对应构造函数,super函数创建this

super函数是调用父类构造函数,返回父类实例,即父类的this对象。在子类构造函数中,只有调用super函数之后,才可使用this关键字

//父类
    class Sup{
        static getSup(){
            console.log('父类静态方法')
        }
        //构造函数
        constructor(name) {
            this.name=name;
        }
        //实例方法
        getName() {
            console.log('name:',this.name)
        }
    }
    //子类继承父类
    class Sub extends Sup{
        constructor(name,age) {
            super(name);    //调用父类构造函数
            this.age=age;
        }
        getAge(){
            console.log('age:',this.age);
        }
    }
    let sub=new Sub('子类',18);
    sub.getName();  //name: 子类
    sub.getAge();   //age: 18
    Sup.getSup();   //只能父类调用父类的静态方法

参考