返回

JS 中的继承大法:从原型链到 ES6 类

前端

JS 中的继承:深入浅出

何为继承?

想象一下,你正在建造一栋房子。你有一份已经建好的蓝图,里面包含了房子的基本结构、布局和功能。现在,你想建造另一栋相似的房子,但你不想从头开始。这时候,你就可以利用“继承”的理念,将第一栋房子的蓝图作为基础,并在其基础上进行修改,建造出第二栋独一无二的房子。

在面向对象编程(OOP)中,继承正是这个道理。继承允许一个对象(称为“子对象”)从另一个对象(称为“父对象”)那里继承属性和方法。这意味着子对象可以拥有与父对象类似的功能,同时还可以添加或修改自己的独特功能。

JS 中的继承方式

在 JavaScript(JS)中,有多种实现继承的方式。每种方式都有其优缺点,开发者可以根据自己的需要选择合适的继承方式。

1. 原型链继承

原型链继承是最基本的 JS 继承方式。每个 JS 对象都有一个“原型对象”,它包含了该对象的属性和方法。当一个对象被创建时,它会将父对象的原型对象链接到自己的原型对象链中。这样,子对象就可以从父对象的原型对象中继承属性和方法。

代码示例:

function Parent() {
  this.name = "Parent";
  this.sayHello = function() {
    console.log("Hello from Parent!");
  };
}

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

// 将 Parent 的原型对象链接到 Child 的原型对象链
Child.prototype = new Parent();

const child = new Child();

console.log(child.name); // 输出: "Parent"
child.sayHello(); // 输出: "Hello from Parent!"

优点:

  • 实现简单,易于理解。
  • 可以在运行时动态添加或修改原型对象。

缺点:

  • 如果父对象的原型对象发生改变,子对象也会受到影响。
  • 无法继承父对象的私有属性和方法。

2. 隐式继承

隐式继承是通过构造函数来实现的。当一个构造函数被调用时,它会创建一个新的对象,并将其作为参数传递给父构造函数。这样,新对象就继承了父对象的所有属性和方法。

代码示例:

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

Parent.prototype.sayHello = function() {
  console.log(`Hello from ${this.name}!`);
};

function Child(name, age) {
  // 调用父构造函数,将 this 传递给父构造函数
  Parent.call(this, name);

  this.age = age;
}

const child = new Child("John", 25);

console.log(child.name); // 输出: "John"
child.sayHello(); // 输出: "Hello from John!"

优点:

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

缺点:

  • 无法继承父对象的原型对象。
  • 如果父构造函数的参数发生改变,子构造函数也需要相应修改。

3. 委托继承

委托继承是通过调用父对象的属性和方法来实现的。这种方式可以继承父对象的原型对象,但需要显式地调用父对象的属性和方法。

代码示例:

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

Parent.prototype.sayHello = function() {
  console.log(`Hello from ${this.name}!`);
};

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

// 委托父对象的 sayHello 方法
Child.prototype.sayHello = function() {
  Parent.prototype.sayHello.call(this);
};

const child = new Child("John", 25);

console.log(child.name); // 输出: "John"
child.sayHello(); // 输出: "Hello from John!"

优点:

  • 可以继承父对象的原型对象。
  • 可以选择性地继承父对象的属性和方法。

缺点:

  • 需要显式地调用父对象的属性和方法。
  • 如果父对象的原型对象发生改变,子对象的委托可能会失效。

4. 组合继承

组合继承是原型链继承和委托继承的结合。它可以继承父对象的原型对象,也可以显式地调用父对象的属性和方法。

代码示例:

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

Parent.prototype.sayHello = function() {
  console.log(`Hello from ${this.name}!`);
};

function Child(name, age) {
  // 原型链继承
  Child.prototype = new Parent(name);

  // 委托继承
  Child.prototype.constructor = Child;

  this.age = age;
}

const child = new Child("John", 25);

console.log(child.name); // 输出: "John"
child.sayHello(); // 输出: "Hello from John!"

优点:

  • 继承了原型链继承和委托继承的优点。
  • 可以继承父对象的原型对象,也可以显式地调用父对象的属性和方法。

缺点:

  • 实现复杂,需要同时使用原型链继承和委托继承。
  • 如果父对象的原型对象或构造函数发生改变,子对象的继承可能会失效。

5. ES6 类继承

ES6 中引入了“类”的语法,为 JS 提供了更简洁、更接近其他面向对象语言的继承方式。类继承使用“extends”来指定父类,并且可以继承父类的属性、方法和原型对象。

代码示例:

class Parent {
  constructor(name) {
    this.name = name;
  }

  sayHello() {
    console.log(`Hello from ${this.name}!`);
  }
}

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

    this.age = age;
  }
}

const child = new Child("John", 25);

console.log(child.name); // 输出: "John"
child.sayHello(); // 输出: "Hello from John!"

优点:

  • 语法简洁,易于理解。
  • 可以继承父类的属性、方法和原型对象。

缺点:

  • 仅支持单继承(一个子类只能继承一个父类)。
  • 如果父类的构造函数或原型对象发生改变,子类的继承可能会失效。

常见问题解答

  • JS 中哪种继承方式最常用?
    原型链继承是最基本的 JS 继承方式,也是使用最广泛的。

  • 哪种继承方式最灵活?
    组合继承最灵活,它可以继承父对象的原型对象,也可以显式地调用父对象的属性和方法。

  • 哪种继承方式最适合单继承?
    ES6 类继承最适合单继承,它使用“extends”关键字来指定父类。

  • 哪种继承方式性能最好?
    原型链继承性能最好,因为它不需要调用父构造函数或显式地委托父对象。

  • 哪种继承方式最容易维护?
    ES6 类继承最容易维护,因为它提供了简洁、易于理解的语法。