【js篇】JavaScript 对象继承的 6 种方式:从原型链到寄生组合

47 阅读4分钟

在 JavaScript 中,继承是实现代码复用和构建对象体系的核心机制。由于 JS 是基于 原型(prototype) 的语言,其继承方式与传统面向对象语言(如 Java、C++)截然不同。

本文将系统解析 6 种继承方式,深入剖析每种方式的原理、优缺点,并提供可运行代码示例。


一、原型链继承(Prototype Chain Inheritance)

✅ 核心:子类原型指向父类实例

function SuperType() {
  this.name = 'Super';
  this.colors = ['red', 'blue'];
}

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

function SubType() {
  this.subName = 'Sub';
}

// 继承:SubType 继承 SuperType
SubType.prototype = new SuperType();

const instance1 = new SubType();
const instance2 = new SubType();

instance1.colors.push('green');
console.log(instance2.colors); // ['red', 'blue', 'green'] — 被污染!

✅ 优点

  • 父类方法可通过原型共享;
  • 支持 instanceofisPrototypeOf()

❌ 缺点

  1. 引用类型被所有实例共享
    • 修改一个实例的引用属性会影响所有实例;
  2. 无法向父类构造函数传参
    // 无法在创建 SubType 时传递 name
    

⚠️ 结论:基本不可用,仅作理论理解。


二、借用构造函数(Constructor Stealing / Classic Inheritance)

✅ 核心:在子类构造函数中调用父类构造函数

function SuperType(name) {
  this.name = name;
  this.colors = ['red', 'blue'];
}

function SubType(name, age) {
  // 继承属性并传参
  SuperType.call(this, name);
  this.age = age;
}

const instance1 = new SubType('Alice', 25);
const instance2 = new SubType('Bob', 30);

instance1.colors.push('green');
console.log(instance2.colors); // ['red', 'blue'] — 不受影响 ✅

✅ 优点

  • ✅ 可向父类传递参数;
  • ✅ 每个实例有独立的属性,避免引用共享问题。

❌ 缺点

  • 无法继承父类原型上的方法
    instance1.sayName(); // ❌ TypeError: not a function
    
  • ❌ 所有方法都在构造函数中定义,无法复用,浪费内存。

⚠️ 结论:解决了属性共享问题,但破坏了方法复用。


三、组合继承(Combination Inheritance)—— 最常用

✅ 核心:构造函数 + 原型链

function SuperType(name) {
  this.name = name;
  this.colors = ['red', 'blue'];
}

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

function SubType(name, age) {
  // 第二次调用 SuperType()
  SuperType.call(this, name); // 继承属性
  this.age = age;
}

// 第一次调用 SuperType()
SubType.prototype = new SuperType(); // 继承方法
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function() {
  console.log(this.age);
};

const instance1 = new SubType('Alice', 25);
instance1.sayName(); // Alice
instance1.sayAge();  // 25

✅ 优点

  • ✅ 支持传参;
  • ✅ 属性独立,方法共享;
  • ✅ 完整的继承链。

❌ 缺点

  • 父类构造函数被调用两次
    1. new SuperType() 设置原型;
    2. SuperType.call(this) 初始化实例;
  • ❌ 子类原型上存在不必要的属性(来自第一次调用):
    console.log(SubType.prototype.name); // 'undefined'(但存在)
    

适用场景:ES6 之前的最通用继承方式


四、原型式继承(Prototypal Inheritance)

✅ 核心:基于已有对象创建新对象

function object(o) {
  function F() {}
  F.prototype = o;
  return new F();
}

const person = {
  name: 'Default',
  friends: ['John', 'Jane']
};

const anotherPerson = object(person);
anotherPerson.name = 'Alice';
anotherPerson.friends.push('Bob');

const yetAnotherPerson = object(person);
console.log(yetAnotherPerson.friends); 
// ['John', 'Jane', 'Bob'] — 被污染!❌

✅ ES5 内建方法:Object.create()

const alice = Object.create(person, {
  name: { value: 'Alice' }
});

✅ 优点

  • 语法简单,适合临时对象扩展。

❌ 缺点

  • 与原型链继承相同:引用类型共享

适用场景:对象间简单继承,非构造函数场景。


五、寄生式继承(Parasitic Inheritance)

✅ 核心:封装继承过程,增强对象

function createAnother(original) {
  const clone = Object.create(original); // 原型式继承
  
  // 增强:添加新方法
  clone.sayHi = function() {
    console.log('Hi!');
  };
  
  return clone;
}

const person = { name: 'Default' };
const newPerson = createAnother(person);
newPerson.sayHi(); // Hi!

✅ 优点

  • 可以在不修改原对象的情况下扩展功能;
  • 灵活。

❌ 缺点

  • ❌ 方法在每次创建时都重新生成,无法复用
  • ❌ 类型识别困难。

适用场景:需要为对象动态添加功能的场景。


六、寄生组合继承(Parasitic Combination Inheritance)—— 最佳实践

✅ 核心:避免两次调用父类构造函数

function inheritPrototype(SubType, SuperType) {
  const prototype = Object.create(SuperType.prototype); // 关键:使用父类原型的副本
  prototype.constructor = SubType;
  SubType.prototype = prototype;
}

function SuperType(name) {
  this.name = name;
  this.colors = ['red', 'blue'];
}

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

function SubType(name, age) {
  SuperType.call(this, name); // 只调用一次
  this.age = age;
}

// 核心:避免 new SuperType()
inheritPrototype(SubType, SuperType);

SubType.prototype.sayAge = function() {
  console.log(this.age);
};

const instance = new SubType('Alice', 25);
instance.sayName(); // Alice
instance.sayAge();  // 25

✅ 优点

  • ✅ 避免了组合继承中两次调用父类构造函数的问题;
  • ✅ 方法共享,属性独立;
  • ✅ 内存效率最高;
  • ✅ 完整的继承链。

❌ 缺点

  • 代码略复杂。

适用场景:ES6 之前的最优继承方案


七、ES6 Class 继承(现代标准)

class Person {
  constructor(name) {
    this.name = name;
  }
  sayName() {
    console.log(this.name);
  }
}

class Student extends Person {
  constructor(name, grade) {
    super(name);
    this.grade = grade;
  }
  sayGrade() {
    console.log(this.grade);
  }
}

本质extendssuper寄生组合继承的语法糖,但更简洁、安全。


八、总结:继承方式对比

方式传参方法复用引用共享调用次数推荐度
原型链N/A
借用构造N/A⭐⭐
组合继承2次⭐⭐⭐⭐
原型式N/A⭐⭐
寄生式N/A⭐⭐⭐
寄生组合1次⭐⭐⭐⭐⭐

💡 结语

“寄生组合继承是 JavaScript 经典继承的巅峰之作。”

它完美解决了:

  • 构造函数重复调用;
  • 引用类型共享;
  • 方法无法复用等问题。

class 的出现,让这一切变得极其简单

掌握这些继承方式,不仅能应对面试,更能深入理解 JavaScript 的原型机制。