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

原型链继承

将父类的实例作为子类的原型

// Shape - 父类(superclass)
function Shape() {
  this.name = "123";
  this.color = [1,2,3];
}
// 父类的方法
Shape.prototype.getName= function() {
  console.log(this.name);
};
// Rectangle - 子类(subclass)
function Rectangle() {
}
Rectangle.prototype = new Shape();
var child = new Rectangle();
child.getName();

缺点:引用类型的属性被所有实例共享

// Shape - 父类(superclass)
function Shape() {
  this.name = "123";
  this.color = [1,2,3];
}
// 父类的方法
Shape.prototype.getName= function() {
  console.log(this.name);
};
// Rectangle - 子类(subclass)
function Rectangle() {
}
Rectangle.prototype = new Shape();
var child = new Rectangle();
var child2 = new Rectangle();
child.getName();
child.color.push('yellow');
console.log(child.color);  //打印输出 [1, 2, 3, "yellow"]
console.log(child2.color); //打印输出 [1, 2, 3, "yellow"]

缺点2:无法在不影响其它实例的前提下向父类传递参数

 // Shape - 父类(superclass)
    function Shape(color) {
        this.name = "123";
        this.color = color;
    }
    // 父类的方法
    Shape.prototype.getName= function() {
        console.log(this.name);
    };
    // Rectangle - 子类(subclass)
    function Rectangle() {

    }
    Rectangle.prototype = new Shape([4,5,6])
    var child = new Rectangle();
    var child2 = new Rectangle();
    console.log(child.color);
    console.log(child2.__proto__.color);

构造函数继承

复制父类构造函数内的属性

// Shape - 父类(superclass)
    function Shape(age) {
        this.name = "123";
        this.color = [1,2,3];
        this.age = age;
        this.setName = function () {
            this.name = "456";
        }
    }
    // 父类的方法
    Shape.prototype.getName= function() {
        console.log(this.name);
    };
    // Rectangle - 子类(subclass)
    function Rectangle(age) {
        //向父类传递age参数
        Shape.call(this,age)  //或使用  Shape.apply(this) 差别在于传参方式不同
    }

    var child = new Rectangle(10);
    var child2 = new Rectangle(18);
    //避免了引用类型的属性被所有实例共享
    child.color.push('yellow');
    console.log(child.color);  //输出打印 [1, 2, 3, "yellow"]
    console.log(child2.color); //输出打印 [1, 2, 3]
    //只能继承父类的实例属性和方法,
    child.setName();
    console.log(child.name);  //输出打印 456
    console.log(child2.name); //输出打印 123
    
    //无法实现复用,每个子类都有父类实例函数的副本,影响性能
    console.log(child);
    console.log(child2);
    child.getName(); //不能继承原型属性/方法 报错

优点:1.避免了引用类型的属性被所有实例共享 2.可以向父类传递参数
缺点:1.只能继承父类的实例属性和方法,不能继承原型属性/方法 2.无法实现复用,每个子类都有父类实例函数的副本,影响性能


image.png

组合继承

结合原型链继承和构造函数继承通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用

 // Shape - 父类(superclass)
    function Shape(age) {
        this.name = "123";
        this.color = [1,2,3];
        this.age = age;
    }
    // 父类的方法
    Shape.prototype.getName= function() {
        console.log(this.name);
    };
    // Rectangle - 子类(subclass)
    function Rectangle(age) {
        //向父类传递age参数
        Shape.call(this,age)  //或使用  Shape.apply(this) 差别在于传参方式不同
    }
    Rectangle.prototype = new Shape();
    var child = new Rectangle(10);
    var child2 = new Rectangle(18);
    child.color.push('yellow');
    console.log(child.color);  //输出打印 [1, 2, 3, "yellow"]
    console.log(child2.color); //输出打印 [1, 2, 3]
    console.log(child);
    console.log(child2);
    child.getName();

优点:融合原型链继承和构造函数的优点,是JavaScript中最常用的继承模式
缺点:调用了两次父类构造函数,父类中的实例属性和方法既存在于子类的实例中,又存在于子类的原型中,不过仅是内存占用,因此,在使用子类创建实例对象时,其原型中会存在两份相同的属性/方法


image.png

组合继承(优化1)

让子类原型引用父类原型(剔除已有的父类的实例属性和方法,只要父类的原型属性/方法,同时减少了一次父类函数调用)

 // Shape - 父类(superclass)
    function Shape(age) {
        this.name = "123";
        this.color = [1,2,3];
        this.age = age;
    }
    // 父类的方法
    Shape.prototype.getName= function() {
        console.log(this.name);
    };
    // Rectangle - 子类(subclass)
    function Rectangle(age) {
        //向父类传递age参数
        Shape.call(this,age)  //或使用  Shape.apply(this) 差别在于传参方式不同
    }
    Rectangle.prototype = Shape.prototype;  //这里让子类原型引用父类原型
    var child = new Rectangle(10);
    var child2 = new Rectangle(18);
    child.color.push('yellow');
    console.log(child.color);  //输出打印 [1, 2, 3, "yellow"]
    console.log(child2.color); //输出打印 [1, 2, 3]
    console.log(child);
    console.log(child2);
    child.getName();
image.png

缺点:如上图发现Rectangle的原型的构造器constructor成了Shape,按照我们的理解应该是Rectangle,这就造成了构造器紊乱,

组合继承(优化2)

指定子类原型构造器constructor为自身

  // Shape - 父类(superclass)
    function Shape(age) {
        this.name = "123";
        this.color = [1,2,3];
        this.age = age;
    }
    // 父类的方法
    Shape.prototype.getName= function() {
        console.log(this.name);
    };
    // Rectangle - 子类(subclass)
    function Rectangle(age) {
        //向父类传递age参数
        Shape.call(this,age)  //或使用  Shape.apply(this) 差别在于传参方式不同
    }
    Rectangle.prototype = Shape.prototype;  //这里让子类原型引用父类原型
    Rectangle.prototype.constructor = Rectangle;   //让子类原型构造器指向自身
    var child = new Rectangle(10);
    var child2 = new Rectangle(18);
    child.color.push('yellow');
    console.log(child.color);  //输出打印 [1, 2, 3, "yellow"]
    console.log(child2.color); //输出打印 [1, 2, 3]
    console.log(child);
    console.log(child2);
    child.getName();

    //这时我们打印子类构造器能正确打印结果
    console.log(Rectangle.prototype.constructor)  //Rectangle
    //但是我们打印父类构造器出现了紊乱 也指向了子类Rectangle 因为父类原型是引用类型 当使用赋值操作时子类原型引用的是父类原型所指向的内存地址,当我们改变子类的原型constructor时同时也改变的父类的原型 constructor
    console.log(Shape.prototype.constructor) // Rectangle

缺点:因为引用类型的特性,导致在修改子类的原型构造器同时也改变的父类的原型构造器

组合继承(优化3

设置一个中间对象,将中间对象的原型指向父类的原型

  // Shape - 父类(superclass)
    function Shape(age) {
        this.name = "123";
        this.color = [1,2,3];
        this.age = age;
    }
    // 父类的方法
    Shape.prototype.getName= function() {
        console.log(this.name);
    };
    // Rectangle - 子类(subclass)
    function Rectangle(age) {
        //向父类传递age参数
        Shape.call(this,age)  //或使用  Shape.apply(this) 差别在于传参方式不同
    }
    //设置中间对象(方式一)
    function f(){}
    f.prototype = Shape.prototype;
    Rectangle.prototype = new f();

    //上面三行代码也可以简化成(方式二)
    //Rectangle.prototype = Object.create(Shape.prototype);

    Rectangle.prototype.constructor = Rectangle

    var child = new Rectangle(10);
    var child2 = new Rectangle(18);
    child.color.push('yellow');
    console.log(child.color);  //输出打印 [1, 2, 3, "yellow"]
    console.log(child2.color); //输出打印 [1, 2, 3]
    console.log(child);
    console.log(child2);
    child.getName();

ES6实现继承

class Shape {
        constructor (age) {
            this.name = "123";
            this.color = [1,2,3];
            this.age = age;
        }

        getName () {
           console.log(this.name)
        }
    }

    class Rectangle extends Shape {
        constructor (age) {
            super(age)
        }
    }

    const o1 = new Rectangle(10);
    const o2 = new Rectangle(18);
    o1.color.push(4)
    console.log(o1.color)
    console.log(o2.color)
    o1.getName()
    console.log(o1)
    console.log(new Rectangle instanceof Shape)

相关资料

new 运算符
constructor
Object.create()
Object.assign()
深入理解JavaScript原型链与继承

你可能感兴趣的:(JavaScript继承(原型链继承、构造函数继承、组合继承))