人人都会AI编程

原型式继承、寄生式继承、寄生组合式继承

更新时间:2026-07-11

原型式继承的核心思想是:不需要定义一个“类”或构造函数,而是直接以一个现有对象为模板,创建一个新对象,让新对象的原型指向这个模板对象。道格拉斯·克罗克福德在 2006 年提出这一模式,ES5 将它内置为 Object.create() 方法。

// 模拟 Object.create 的简单版本
function createObject(proto) {
  function F() {}
  F.prototype = proto;
  return new F();
}

const parent = {
  name: 'Parent',
  greet() {
    return `I am ${this.name}`;
  }
};

const child = Object.create(parent);
child.name = 'Child';

console.log(child.greet()); // "I am Child"

工作流程:

  1. 创建一个临时构造函数,将其 prototype 指向传入的原型对象。
  2. new 调用该临时构造函数,返回一个全新对象,该对象的 proto 指向原型对象。
  3. 新对象本身是空的,但可以访问原型链上的属性和方法。

优点

  • 极其简洁:不用写构造函数,不需要 new 关键字,非常适合“一个对象需要继承另一个对象”的场景。
  • 基于实例而非类型:符合 JavaScript 的“对象间委托”思想,没有类的概念负担。

缺点

  • 引用类型共享问题:如果原型对象中包含数组或对象等引用类型,所有实例会共享同一个引用,一个实例修改会影响到其他实例(这与原型链继承的缺点类似)。
  • 无法初始化参数:原型式继承直接从对象创建,没有类似构造函数传递定制参数的过程,无法对每个实例进行差异化初始化,通常需要手动添加或覆盖属性。

实际应用
原型式继承最常见的就是 Object.create(null) 创建纯字典对象(没有原型链上的属性和方法干扰),以及需要轻量级对象扩展时。在 Vue.js 的响应式系统中,也使用 Object.create 来完成某些原型委托。


寄生式继承

寄生式继承可以看作是原型式继承的增强版:创建一个仅用于封装继承过程的函数,在内部用原型式继承得到一个对象,然后对这个对象进行扩充(添加方法或属性),最后返回这个对象。这种“寄生”在已有对象上、再添枝加叶后再交出去的思路,类似于工厂模式,但基础来自原型式继承。

function createParasitic(original) {
  // 1. 通过原型式继承创建新对象
  const clone = Object.create(original);
  
  // 2. 在新对象上添加自有方法或属性
  clone.sayHi = function() {
    return `Hi, I am ${this.name}`;
  };
  
  return clone;
}

const person = {
  name: 'Anonymous',
  greet() {
    return `Hello, I am ${this.name}`;
  }
};

const student = createParasitic(person);
student.name = 'Tom';
console.log(student.greet());  // "Hello, I am Tom"
console.log(student.sayHi());  // "Hi, I am Tom"

优点

  • 可以看作是带自增强功能的工厂函数:不必创建构造函数或类,用函数封装继承与扩展逻辑,代码组织比较清晰。
  • 适合只想一次性扩展某个对象,且不需要建立通用“类型”的场景。

缺点

  • 方法无法复用createParasitic 每调用一次,都会在内部分配新的函数(如 sayHi),不同实例的 sayHi 并不共享,造成内存浪费。这与构造函数模式类似。
  • 基础的引用类型共享问题依然存在(因为原型式继承是基础)。

适用场景
寄生式继承通常不单独使用,更多是作为组合继承或寄生组合式继承中的一个中间步骤。如果场景很小,只需要一个临时扩展的对象,比如需要对某个配置对象做预处理,然后交给其他模块使用,寄生式继承就能以最小成本完成任务。


寄生组合式继承

寄生组合式继承被视为 JavaScript 中最理想的继承方式,它结合了原型式继承的优点,并解决了“组合继承”(构造函数继承 + 原型链继承)中“父构造函数被调用两次”的性能问题。ES6 的 class extends 在底层本质上也是用寄生组合式继承实现的。

先回顾一下组合继承的问题:
组合继承为了同时继承父类的自有属性和原型方法,会执行:

  1. 在子构造函数内执行 Parent.call(this, args) —— 第一次调用父构造函数,得到一组子实例的自有属性。
  2. 执行 Child.prototype = new Parent() —— 第二次调用父构造函数,这次会创建一组挂载在原型上的属性(这些属性会被子实例的自有属性屏蔽,形成冗余)。

寄生组合式继承的改进思路:用原型式继承创建一个干净的、指向父类原型的对象,把它作为子类的原型,从而避免“new Parent()”的第二次调用。

function inheritPrototype(Child, Parent) {
  // 1. 创建以 Parent.prototype 为原型的对象
  const prototype = Object.create(Parent.prototype);
  
  // 2. 修正 constructor 指向
  prototype.constructor = Child;
  
  // 3. 将这个对象赋给子类的 prototype
  Child.prototype = prototype;
}

// 父类
function Animal(name) {
  this.name = name;
  this.colors = ['red', 'blue'];
}
Animal.prototype.sayName = function() {
  return this.name;
};

// 子类
function Dog(name, breed) {
  Animal.call(this, name);   // 第一次(也是唯一一次)调用父构造函数,继承自有属性
  this.breed = breed;
}

// 关键步骤:寄生组合式继承
inheritPrototype(Dog, Animal);

// 子类原型上添加方法必须在原型替换之后
Dog.prototype.bark = function() {
  return `${this.name} barks!`;
};

const dog1 = new Dog('Buddy', 'Golden');
const dog2 = new Dog('Max', 'Husky');

console.log(dog1.sayName());         // 'Buddy'
console.log(dog1.bark());           // 'Buddy barks!'
console.log(dog1.colors);           // ['red', 'blue']
dog1.colors.push('green');
console.log(dog2.colors);           // ['red', 'blue']  ——互不影响
console.log(dog1 instanceof Dog);   // true
console.log(dog1 instanceof Animal);// true

核心要点

  • 只调用一次父构造函数 Animal.call(this),获取父类中的实例属性(避免原型上出现冗余属性)。
  • 子类的原型通过 Object.create(Parent.prototype) 创建,它完美地复制了父类原型上的方法,而不包含父类的实例属性。
  • 必须修正 constructor 指向为子类自身,否则实例的 constructor 会错误地指向父类。
  • 添加子类原型方法要在原型替换之后进行。

为什么说它是最理想的?

  1. 性能高:父构造函数只执行一次,没有重复初始化实例属性。
  2. 原型链清晰Child.prototype → (干净对象) → Parent.prototype,避免在子类原型上挂载不必要的父类属性。
  3. 符合语义:完全实现了面向对象继承所需要的特性:子类继承父类属性和方法,原型方法与实例属性分离,且可以自由扩展。

与 ES6 class 的关系

class Animal {
  constructor(name) { this.name = name; }
  sayName() { return this.name; }
}
class Dog extends Animal {
  constructor(name, breed) {
    super(name);
    this.breed = breed;
  }
  bark() { return `${this.name} barks!`; }
}

编译成 ES5 后,extends 的实现就是用寄生组合式继承。也就是说,寄生组合式继承是 JavaScript 继承的成熟范式,也是 modern JavaScript 引擎和写法背后的核心继承机制

实际应用
今天你已经不必手工写 inheritPrototype,直接用 class ... extends 即可。但理解寄生组合式继承能帮你:

  • 看懂经典库的继承代码。
  • 在面试中深入解释“ES6 类的本质”。
  • 在需要极致的控制力(如按需 mixin)时,用原型式技巧写出更灵活的方案。