likes
comments
collection
share

继承的多种⽅式&优缺点

作者站长头像
站长
· 阅读数 3

继承是什么?

继承(inheritance)是面向对象软件技术当中的一个概念。继承可以使得子类具有父类别的各种属性方法,而不需要再次编写相同的代码。

如果一个类别B“继承自”另一个类别A,就把这个B称为“A的子类”,而把A称为“B的父类别”也可以称“A是B的超类”。

继承的多种方式

故事背景:作者有两只喵,大儿金吉拉(小白),小儿布偶猫(小布),默认他们的爸爸都爱吃饭和睡觉,毕竟他们也爱吃饭和睡觉,大儿聒噪(哼小曲),小儿优雅乖巧,论二胎家庭是无法做到一碗水端平的吧

1. 原型链继承(Prototype Inheritance):

  • 实现方式: 子类构造函数的原型对象指向父类的实例。
    • 优点: 简单易懂,易于实现。
    • 缺点:
      • 引用类型的属性会被所有实例共享,存在属性共享的问题。
      • 不能向父类传递参数, 无法通过子类向父类传参。
function Parent() {
    this.name = '爸爸'
    this.hobbies = ['吃饭','睡觉'];
}

Parent.prototype.getHobbies = function() {
    console.log(this.name + '的爱好是' + this.hobbies.join('和'));
};

function Child(name) {
    this.name = name;
    this.hobbies = ['哼小曲'];
}

Child.prototype = new Parent();

var parent = new Parent();
parent.getHobbies(); //爸爸的爱好是吃饭和睡觉

var child = new Child('小白');
child.getHobbies(); //小白的爱好是哼小曲

问题1:引⽤类型的属性被所有实例共享,举个例⼦:

function Parent() {
    this.name = '爸爸'
    this.hobbies = ['吃饭','睡觉'];
}

Parent.prototype.getHobbies = function() {
    console.log(this.name + '的爱好是' + this.hobbies.join('和'));
};

function Child(name) {
    this.name = name;
}

// 子类的原型指向父类的实例
Child.prototype = new Parent();

var child1 = new Child('小白');
var child2 = new Child('小布');

child1.getHobbies(); // 小白的爱好是吃饭和睡觉
child2.getHobbies(); // 小布的爱好是吃饭和睡觉

// 修改小白的爱好,小布的爱好也跟着变了
child1.hobbies.push('哼小曲');

child1.getHobbies(); // 小白的爱好是吃饭和睡觉和哼小曲
child2.getHobbies(); // 小布的爱好是吃饭和睡觉和哼小曲

问题2:不能向父类传递参数,举个例⼦:

function Parent(name) {
    this.name = name || '爸爸';
}

Parent.prototype.sayHello = function() {
    console.log('你好, 我是' + this.name);
};

function Child() {
    // 子类构造函数中没有传递参数给父类
}

Child.prototype = new Parent();

var child1 = new Child('小布');
child1.sayHello(); // 你好, 我是爸爸

子类虽然传了名字,依然输出爸爸的名字,向父类传递参数失败

2. 构造函数继承(Constructor Inheritance):

  • 实现方式: 在子类构造函数中调用父类构造函数,并使用 callapply 方法绑定 this
    • 优点:

      • 避免了属性共享问题。
      • 可以向父类传递参数。
    • 缺点:

      • 不能继承父类原型上的方法。
      • 每次创建子类实例都会创建一份父类方法的副本,存在内存浪费。
function Parent(name) {
    this.name = name || '爸爸';
    this.hobbies = ['吃饭','睡觉'];
}

function Child(name) {
    Parent.call(this, name);
}

var parent = new Parent();
console.log(parent.name + '的爱好是' + parent.hobbies.join('和')); // 爸爸的爱好是吃饭和睡觉
var child1 = new Child('小白');
child1.hobbies.push('哼小曲');
console.log(child1.name + '的爱好是' + child1.hobbies.join('和')); // 小白的爱好是吃饭和睡觉和哼小曲
var child2 = new Child('小布');
console.log(child2.name + '的爱好是' + child2.hobbies.join('和')); // 小布的爱好是吃饭和睡觉

问题1: 不能继承父类原型上的方法

function Parent(name) {
    this.name = name || '爸爸';
    this.hobbies = ['吃饭','睡觉'];
}
Parent.prototype.sayHello = function() {
    console.log('你好, 我是' + this.name);
};

function Child(name) {
    Parent.call(this, name);
}

var parent = new Parent();
parent.sayHello() //你好, 我是爸爸
var child1 = new Child('小白');
child1.sayHello() // child1.sayHello is not a function

3. 组合继承(Combination Inheritance):

  • 实现方式: 同时使用原型链继承和构造函数继承。

  • 优点:

    • 解决了原型链继承和构造函数继承各自的缺点。
    • 既可以继承父类原型上的方法,又可以避免属性共享问题。
  • 缺点:

    • 子类原型链上存在两份相同的属性,存在内存浪费。
function Parent(name) {
    this.name = name || '爸爸';
    this.hobbies = ['吃饭','睡觉'];
}

Parent.prototype.getHobbies = function() {
    console.log(this.name + '的爱好是' + this.hobbies.join('和'));
};

function Child (name) {
    Parent.call(this, name);
}

Child.prototype = new Parent();
Child.prototype.constructor = Child;

var child1 = new Child('小白');
child1.hobbies.push('哼小曲');
child1.getHobbies() //小白的爱好是吃饭和睡觉和哼小曲

var child2 = new Child('小布');
child2.getHobbies() //小布的爱好是吃饭和睡觉

融合原型链继承和构造函数的优点,是 JavaScript 中最常⽤的继承模式。

4. 原型式继承(Prototype Pattern):

  • 实现方式: 利用一个空的构造函数作为中介,创建一个对象并将该对象的原型指向某个对象。
  • 优点: 简单方便,可以创建多个对象,共享同一个原型。
  • 缺点:
    • 无法传递参数。
    • 引用类型的属性会被所有实例共享,存在属性共享的问题。
function createObj(o) {
     function F(){}
     F.prototype = o;
     return new F();
}

var parent = {
    name: '爸爸',
    hobbies: ['吃饭','睡觉'],
    getHobbies: function() {
        console.log(this.name + '的爱好是' + this.hobbies.join('和'));
    }
};

var child1 = createObj(parent);
child1.name = '小白';
child1.hobbies.push('哼小曲');
child1.getHobbies(); // 小白的爱好是吃饭和睡觉和哼小曲

var child2 = createObj(parent);
child1.name = '小布';
child1.getHobbies(); // 小布的爱好是吃饭和睡觉和哼小曲

缺点:包含引⽤类型的属性值始终都会共享相应的值,这点跟原型链继承⼀样。

5. 寄生式继承(Parasitic Inheritance):

  • 实现方式: 在原型式继承的基础上,对对象进行扩展,并返回扩展后的对象。
  • 优点: 在不破坏原型链的情况下,对对象进行扩展。
  • 缺点: 无法传递参数,存在属性共享的问题。
function createObj(original) {
    var clone = Object.create(original);
    clone.getHobbies = function() {
        console.log(this.name + '的爱好是' + this.hobbies.join('和'));
    };
    return clone;
}

var parent = {
    name: '爸爸',
    hobbies: ['吃饭','睡觉'],
};

var child1 = createObj(parent);
child1.name = '小白';
child1.hobbies.push('哼小曲');
child1.getHobbies(); // 小白的爱好是吃饭和睡觉和哼小曲

var child2 = createObj(parent);
child2.name = '小布';
child2.getHobbies(); // 小布的爱好是吃饭和睡觉和哼小曲

6. 寄生组合式继承(Parasitic Combination Inheritance):

  • 实现方式: 使用寄生式继承来继承父类原型,然后将结果指定给子类的原型。
  • 优点: 解决了组合继承中父类被调用两次构造函数的
// 父类构造函数
function Parent(name) {
    this.name = name || '爸爸';
    this.hobbies = ['吃饭','睡觉'];
}

// 父类原型方法
Parent.prototype.getHobbies = function() {
    console.log(this.name + '的爱好是' + this.hobbies.join('和'));
};

// 子类构造函数
function Child (name) {
    Parent.call(this, name);
}

// 使用寄生方式继承父类的原型方法
function inheritPrototype(subType, superType) { 
    var prototype = Object.create(superType.prototype); 
    prototype.constructor = subType; 
    subType.prototype = prototype; 
}

// 寄生组合式继承 
inheritPrototype(Child, Parent);

// 实例化子类 
var child1 = new Child('小白');
child1.hobbies.push('哼小曲');
child1.getHobbies(); //小白的爱好是吃饭和睡觉和哼小曲

var child2 = new Child('小布');
child2.getHobbies(); // 小布的爱好是吃饭和睡觉
转载自:https://juejin.cn/post/7351581179017887778
评论
请登录