返回

浅析 JS 实现继承的多种途径及优劣对比

前端

JavaScript 中的继承:一种面向对象编程基础

在 JavaScript 中,继承是面向对象编程中至关重要的基础,允许子类复用父类的属性和方法,从而简化代码编写,提高代码可读性和可维护性。那么,JavaScript 中有哪些实现继承的方法呢?每种方法的优缺点又是什么?本文将带领你深入探讨这些问题,为你提供有益的见解。

构造函数继承:简单直接

构造函数继承是最简单、最直接的 JavaScript 继承方式。子类的构造函数通过调用父类的构造函数来继承父类的属性和方法。

// 父类
function Person(name, age) {
  this.name = name;
  this.age = age;
}

// 子类
function Student(name, age, school) {
  // 调用父类的构造函数
  Person.call(this, name, age);

  this.school = school;
}

优点:

  • 实现简单,容易理解。
  • 可以继承父类的私有属性和方法。

缺点:

  • 每次创建子类实例时,都需要调用父类的构造函数,可能会造成性能开销。
  • 子类无法重写父类的方法。

原型链继承:避免性能开销

原型链继承是另一种常见的 JavaScript 继承方式。子类的原型对象指向父类的原型对象,从而继承父类的属性和方法。

// 父类
function Person(name, age) {
  this.name = name;
  this.age = age;
}

// 子类
function Student(name, age, school) {
  // 创建子类的原型对象
  Student.prototype = new Person();

  // 在子类的原型对象上添加属性和方法
  Student.prototype.school = school;
  Student.prototype.study = function() {
    console.log(this.name + " is studying.");
  };
}

优点:

  • 实现相对简单,并且可以避免构造函数继承的性能开销。
  • 子类可以重写父类的方法。

缺点:

  • 无法继承父类的私有属性和方法。
  • 子类的原型对象与父类的原型对象耦合度高,不利于代码维护。

组合继承:融合优势

组合继承将构造函数继承和原型链继承结合起来,既可以继承父类的私有属性和方法,又可以避免构造函数继承的性能开销,还可以让子类重写父类的方法。

// 父类
function Person(name, age) {
  this.name = name;
  this.age = age;
}

// 子类
function Student(name, age, school) {
  // 调用父类的构造函数
  Person.call(this, name, age);

  // 子类的原型对象指向父类的原型对象
  Student.prototype = new Person();

  // 在子类的原型对象上添加属性和方法
  Student.prototype.school = school;
  Student.prototype.study = function() {
    console.log(this.name + " is studying.");
  };
}

优点:

  • 结合了构造函数继承和原型链继承的优点。

缺点:

  • 实现相对复杂,容易出错。
  • 子类的原型对象与父类的原型对象耦合度高,不利于代码维护。

组合继承(call 和 Object.create):简洁优化

ES5 中引入了 Object.create 方法,我们可以利用 call 方法和 Object.create 方法来实现组合继承。这种方式更加简洁。

// 父类
function Person(name, age) {
  this.name = name;
  this.age = age;
}

// 子类
function Student(name, age, school) {
  // 创建子类的原型对象
  Student.prototype = Object.create(Person.prototype);

  // 在子类的原型对象上添加属性和方法
  Student.prototype.school = school;
  Student.prototype.study = function() {
    console.log(this.name + " is studying.");
  };

  // 调用父类的构造函数
  Person.call(this, name, age);
}

优点:

  • 实现相对简单,可以避免构造函数继承的性能开销。
  • 子类可以重写父类的方法。
  • 子类的原型对象与父类的原型对象耦合度较低,有利于代码维护。

缺点:

  • 无法继承父类的私有属性和方法。

ES6 类:语法简洁

ES6 中引入了 class ,我们可以使用 class 来定义类,从而实现继承。

// 父类
class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
}

// 子类
class Student extends Person {
  constructor(name, age, school) {
    super(name, age); // 调用父类的构造函数
    this.school = school;
  }

  study() {
    console.log(this.name + " is studying.");
  }
}

优点:

  • 语法简洁,易于理解。
  • 可以继承父类的私有属性和方法。
  • 子类可以重写父类的方法。
  • 子类的原型对象与父类的原型对象耦合度较低,有利于代码维护。

缺点:

  • 仅支持 ES6 及以上版本。

总结

继承方式 优点 缺点
构造函数继承 实现简单,可以继承父类的私有属性和方法 每次创建子类实例时,都需要调用父类的构造函数,可能会造成性能开销
原型链继承 实现相对简单,可以避免构造函数继承的性能开销,子类可以重写父类的方法 无法继承父类的私有属性和方法,子类的原型对象与父类的原型对象耦合度高,不利于代码维护
组合继承(组合上面两种) 结合了构造函数继承和原型链继承的优点,既可以继承父类的私有属性和方法,又可以避免构造函数继承的性能开销,还可以让子类重写父类的方法 实现相对复杂,容易出错,子类的原型对象与父类的原型对象耦合度高,不利于代码维护
组合继承(call 和 Object.create) 实现相对简单,可以避免构造函数继承的性能开销,子类可以重写父类的方法,子类的原型对象与父类的原型对象耦合度较低,有利于代码维护 无法继承父类的私有属性和方法
ES6 类 语法简洁,易于理解,可以继承父类的私有属性和方法,子类可以重写父类的方法,子类的原型对象与父类的原型对象耦合度较低,有利于代码维护 仅支持 ES6 及以上版本

常见问题解答

1. 什么是 JavaScript 中的继承?

继承是面向对象编程中的一种机制,允许子类复用父类的属性和方法。

2. JavaScript 中有哪些常见的继承方法?

  • 构造函数继承
  • 原型链继承
  • 组合继承
  • 组合继承(call 和 Object.create)
  • ES6 类

3. 哪种 JavaScript 继承方法最好?

没有一种放之四海而皆准的最佳继承方法,需要根据具体情况选择最适合的方法。

4. 原型链继承和组合继承有什么区别?

原型链继承无法继承父类的私有属性和方法,而组合继承可以。

5. ES6 类有什么优势?

ES6 类语法简洁,易于理解,并且可以继承父类的私有属性和方法。