js 中的继承

js 中的继承

(一)原型链继承

  • 原理: 通过修改构造函数的原型指针指向一个类的实例

  • 不足:不能给父类构造函数传递参数,父类中的引用类型属性共享

    function SuperType() {
        this.property = true;
    }
    SuperType.prototype.getSuperValue = function () {
        return this.property;
    };
    function SubType() {
        this.subproperty = false;
    }
    // 继承 SuperType
    SubType.prototype = new SuperType(); // 原型变成了SuperType的实例
    // 相当于给SuperType实例添加一个方法
    SubType.prototype.getSubValue = function () {
        return this.subproperty;
    }
    
    let instance = new SubType();
    console.log(instance);
    
    

js 中的继承

分析:subType 的原型指向了 superType 实例

(二)盗用构造函数继承

  • 原理:通过在一个类的构造函数中调用目标类的构造函数并传递this为当前类的this
  • 特点:可以向目标类的构造函数传参,且父类的属性不会共享,但是不能访问目标类的原型,只能获得目标类的构造函数中定义的属性,并保存到当前类的实例中
    // 盗用构造函数
     function SuperType(name) {
         this.name = name;
         this.sayName = function(){
             console.log(name);
         }
     }
     // 原型上
     SuperType.prototype.foo = function(){
         console.log('foo');
     }
    function SubType() {
        // 继承 SuperType 并传参
        SuperType.call(this, "Nicholas");
        // SuperType实例上的属性和方法会复制到本实例中,但是原型上的不会
        // 实例属性
        this.age = 29;
    }
    let instance = new SubType();
	console.log(instance);
    console.log(instance.name); // "Nicholas";
    console.log(instance.age); // 29

js 中的继承

分析:subType 盗用了 superType 的构造函数,把该对象本身的属性都复制了下来,并保存了自身实例当中,而 superType 原型的 foo 没有被"盗用" (复制)下来

(三)原型盗用组合继承

  • 原理:结合1,2的使用

  • 特点:既可以传参,又可以访问父类的原型,但是父类会创建两个实例

        function SuperType(name) {
            this.name = name;
            this.colors = ["red", "blue", "green"];
        }
        SuperType.prototype.sayName = function () {
            console.log(this.name);
        };
        function SubType(name, age) {
            SuperType.call(this, name); // 第二次调用 SuperType()
            this.age = age;
        }
        SubType.prototype = new SuperType(); // 第一次调用 SuperType()
        SubType.prototype.constructor = SubType;// 不写是没有这个属性的
        SubType.prototype.sayAge = function () {
            console.log(this.age);
        };
        let instance = new SubType('abc',29);
        console.log(instance);
    

js 中的继承

分析:盗用构造函数可以在创建原型实例时可以传递参数,但是为了弥补不能盗用原型的原型属性和方法,又适用创建 superType 实例设置 prototype 的方法,虽然现在弥补上述的不足,但是又会多创建原型自身的属性,浪费的内存空间。

(四)原型式继承

  • 原理:还是使用原型进行继承,只是把这个过程封装成一个函数,返回一个自定义对象
  • 特点:和原型链继承一样
function subType(superObj) {
    function F() { }
    F.prototype = superObj;
    return new F();
}
function superType(name, friends) {
    this.name= name,
        this.friends= friends
};
let subInstance = subType(superType("Nicholas",["Shelby", "Court", "Van"])); // 原型传参实例化
subInstance.age = 18; // 目标对象,手动添加属性

console.log(subInstance); 

js 中的继承

分析:与方法一相比,好处是,可以传入一个定义实例作为原型(原型可以传参实例化),但是鱼与熊掌不可兼得,现在目标对象,又不能传参一次性创建实例了,只是手动添加实例。

(五)寄生式继承

  • 原理:在原型式的基础上,在函数中为自定义对象添加新的属性和方法
  • 特点:和原型链继承一样
function subType(superObj) {
    function F() { }
    F.prototype = superObj;
    return new F();
}
function superType(name, friends) {
    this.name= name,
        this.friends= friends
};

// 传入了原型,实例的属性和方法
function createSubType(superObj, {age, sayHi}){
    let sub = subType(superObj); 
    sub.age = age;
    sub.sayHi = sayHi;
    return sub;
}

let subInstance = createSubType(new superType("Nicholas",["Shelby", "Court", "Van"]), {age:18, sayHi(){
    console.log('hi, I am ' + this.age + " years old.");
}});

subInstance.sayHi();

js 中的继承

分析:其实就是在方法四的基础上,又封装了一层,把原来创建实例后的手动写法变成了传入一个对象参数给 createSubType 工厂函数来创建而已,反而变得越来越绕

(六)寄生式组合继承

  • 原理:结合了寄生式和盗用构造函数的原理
  • 特点:在类出现前最优的继承方式,可以传参给父类构造函数,可以访问父类的原型,且只创建一个父类实例
function object(o) {
    function F() { }
    F.prototype = o;
    return new F();
}

// 返回 subType -> object -> supertype.prototype 的三层原型链
function inheritPrototype(subType, superType) {
    //相当与创建superType实例,但是是间接创建, 该实例上没有superType实例应有的属性,但是有原型上的
    let prototype = object(superType.prototype); // 创建对象, 将 superType的原型作为目标实例原型
    prototype.constructor = subType; // 增强对象,
    subType.prototype = prototype; // 赋值对象,subtype 原型设置为 object() 返回的对象
}


function SuperType(name) {
    this.name = name;
    this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function () {
    console.log(this.name);
};
function SubType(name, age) {
    SuperType.call(this, name); //盗用构造函数
    this.age = age;
}
inheritPrototype(SubType, SuperType);
SubType.prototype.sayAge = function () {
    console.log(this.age);
};

let instance = new SubType('kobe', 29);

js 中的继承

分析:这个方法结合 ①盗用构造函数复制supertype本身属性到 subtype,②寄生式继承创建supertype.prototype为原型的中间实例,③将中间实例设置为 subtype.prototype 的方法形成了一条 subType -> object -> supertype.prototype 的三层原型链,从而克服盗用构造函数不能复制原型的原型属性的弊端,这个方法比较完美,但是会比较复杂。

(七)class 关键字 继承语法糖

Class 可以通过extends关键字实现继承,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多

class Point{
    constructor(x,y){
        this.x = x;
        this.y = y;
    }
    toString(){
        return `(${this.x}, ${this.y})`
    }
}
class ColorPoint extends Point{
    constructor(x,y,color){
        super(x,y); // 调用 父类构造方法
        this.color = color;
    }
    toString(){
        return this.color + ' ' + super.toString(); // 调用父类的 toString 方法
    }
}

const colorPoint = new ColorPoint(2,3,'red');
console.log(colorPoint);

js 中的继承

分析:class 关键字的继承方式使用 extends 关键字,子类的构造函数的 super()方法,是调用父类的构造函数,并且把父类自身属性复制到子类实例中,这特性与 盗用构造函数 call() 方法相同,当父类的方法与子类的相同时,要想调用 父类的,可以使用 super.方法名 来调用,super指向的是父类的原型对象,具体 class的继承方式请看 阮一峰老师ES6的class继承文章

上一篇:js之原型链


下一篇:JavaScript深入之从原型到原型链 #2