JavaScript基础:继承的方法及优缺点
JavaScript基础:继承的方法及优缺点

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

原型链继承是 JavaScript 中最早的继承方法之一,它通过将子类的原型指向父类的实例,来实现子类对父类属性和方法的继承。

示例代码:
function Parent() {
  this.name = 'Parent';
  this.colors = ['red', 'blue', 'green'];
}

Parent.prototype.sayName = function() {
  console.log(this.name);
};

function Child() {
  this.age = 18;
}

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

const child1 = new Child();
child1.colors.push('yellow');
console.log(child1.colors);  // ['red', 'blue', 'green', 'yellow']

const child2 = new Child();
console.log(child2.colors);  // ['red', 'blue', 'green', 'yellow']
优点:
  • 子类可以访问父类的属性和方法。
  • 父类的实例方法可以通过子类继承,避免重复定义。
缺点:
  • 引用类型的问题:父类中引用类型(如数组、对象)会被所有子类实例共享,导致一个子类实例的修改会影响其他子类实例。
  • 无法向父类构造函数传参:在创建子类实例时,无法向父类构造函数传递参数,这限制了灵活性。

2. 借用构造函数继承 (Constructor Stealing / Classical Inheritance)

借用构造函数继承,也称为“经典继承”,通过在子类的构造函数中调用父类的构造函数来实现属性的继承。

示例代码:
function Parent(name) {
  this.name = name;
  this.colors = ['red', 'blue', 'green'];
}

function Child(name, age) {
  Parent.call(this, name);  // 调用父类构造函数
  this.age = age;
}

const child1 = new Child('Child1', 18);
child1.colors.push('yellow');
console.log(child1.colors);  // ['red', 'blue', 'green', 'yellow']
console.log(child1.name);    // 'Child1'

const child2 = new Child('Child2', 20);
console.log(child2.colors);  // ['red', 'blue', 'green']
console.log(child2.name);    // 'Child2'
优点:
  • 解决了原型链继承中引用类型共享的问题,因为每次调用父类构造函数时,都会重新创建属性。
  • 可以向父类构造函数传递参数,使继承更加灵活。
缺点:
  • 无法继承父类原型上的方法:父类的实例方法无法被子类继承,所有方法只能写在构造函数内部,这会导致每个子类实例都生成相同的方法,浪费内存。

3. 组合继承 (Combination Inheritance)

组合继承结合了原型链继承和借用构造函数继承的优点:通过原型链继承父类的方法,通过借用构造函数继承父类的属性。

示例代码:
function Parent(name) {
  this.name = name;
  this.colors = ['red', 'blue', 'green'];
}

Parent.prototype.sayName = function() {
  console.log(this.name);
};

function Child(name, age) {
  Parent.call(this, name);  // 借用构造函数继承属性
  this.age = age;
}

Child.prototype = new Parent();  // 原型链继承方法
Child.prototype.constructor = Child;

const child1 = new Child('Child1', 18);
child1.colors.push('yellow');
console.log(child1.colors);  // ['red', 'blue', 'green', 'yellow']
child1.sayName();            // 'Child1'

const child2 = new Child('Child2', 20);
console.log(child2.colors);  // ['red', 'blue', 'green']
child2.sayName();            // 'Child2'
优点:
  • 既可以继承父类的实例属性,也可以继承父类的原型方法。
  • 每个子类实例都有自己的属性,引用类型不会共享,灵活性更高。
缺点:
  • 父类构造函数会被调用两次,一次是在借用构造函数时,另一次是在设置子类原型时,这导致了不必要的性能消耗。

4. 寄生组合继承 (Parasitic Combination Inheritance)

寄生组合继承是对组合继承的优化,它避免了父类构造函数被调用两次的情况。通过这种方式,子类只继承父类原型中的方法,而不调用父类构造函数来设置子类原型。

示例代码:
function Parent(name) {
  this.name = name;
  this.colors = ['red', 'blue', 'green'];
}

Parent.prototype.sayName = function() {
  console.log(this.name);
};

function Child(name, age) {
  Parent.call(this, name);  // 借用构造函数继承属性
  this.age = age;
}

Child.prototype = Object.create(Parent.prototype);  // 创建父类原型的副本
Child.prototype.constructor = Child;

const child1 = new Child('Child1', 18);
child1.colors.push('yellow');
console.log(child1.colors);  // ['red', 'blue', 'green', 'yellow']
child1.sayName();            // 'Child1'

const child2 = new Child('Child2', 20);
console.log(child2.colors);  // ['red', 'blue', 'green']
child2.sayName();            // 'Child2'
优点:
  • 避免了组合继承中的重复调用父类构造函数的问题,提升了性能。
  • 保留了组合继承的优点,能够继承父类的实例属性和原型方法。
缺点:
  • 实现相对复杂,需要手动创建和重设子类的原型。

5. ES6 class 继承 (Class Inheritance)

ES6 引入了 class 语法,使继承变得更加直观和简洁。通过 extends 关键字,子类可以直接继承父类的属性和方法。

示例代码:

class Parent {
  constructor(name) {
    this.name = name;
    this.colors = ['red', 'blue', 'green'];
  }

  sayName() {
    console.log(this.name);
  }
}

class Child extends Parent {
  constructor(name, age) {
    super(name);  // 调用父类构造函数
    this.age = age;
  }
}

const child1 = new Child('Child1', 18);
child1.colors.push('yellow');
console.log(child1.colors);  // ['red', 'blue', 'green', 'yellow']
child1.sayName();            // 'Child1'

const child2 = new Child('Child2', 20);
console.log(child2.colors);  // ['red', 'blue', 'green']
child2.sayName();            // 'Child2'
优点:
  • 语法简洁、直观,类结构清晰。
  • 可以通过 super() 调用父类的构造函数,并继承父类的原型方法。
  • 易于理解和使用,尤其对面向对象编程背景的开发者来说更为友好。
缺点:
  • ES6 class 只是基于原型的语法糖,底层仍然是基于原型链实现的继承。
  • 不能直接用于一些老旧的浏览器,可能需要通过 Babel 等工具进行转译。