← 中文资源

简体中文版本

原型继承

JavaScript 基于原型的对象模型:对象通过内部的 [[Prototype]] 链把属性访问委托给另一个对象。本文解释原型链、class 语法、自有属性与性能注意事项。

阅读英文原文

什么是原型继承

原型继承是指对象可以把属性和方法访问委托给另一个对象(它的原型)。每个 JavaScript 对象都有一个内部的 [[Prototype]] 链接(可通过 Object.getPrototypeOf() 读取,旧式写法是 __proto__)。当访问对象自身不存在的属性时,引擎会沿着原型链向上查找,直到找到该属性或到达 null。

这与基于类的继承不同:JavaScript 没有类蓝图,对象直接继承自其他对象。ES6 的 class 语法本质上是对原型机制的语法封装。

原型链

当你访问 obj.prop 时:

  1. 检查 prop 是否直接存在于 obj 上(自有属性)。
  2. 如果不存在,检查 obj.[[Prototype]] 上是否有 prop。
  3. 继续向上查找,直到找到属性或链结束于 null。
const animal = { eats: true };
const rabbit = Object.create(animal);
rabbit.jumps = true;

console.log(rabbit.jumps); // true(自有属性)
console.log(rabbit.eats);  // true(委托给 animal)

class 如何使用原型

class Animal {
  constructor(name) { this.name = name; }
  speak() { return `${this.name} makes a noise`; }
}
class Dog extends Animal {
  speak() { return `${this.name} barks`; }
}

// 等价的基于原型的写法:
function Animal2(name) { this.name = name; }
Animal2.prototype.speak = function() { return this.name + ' makes a noise'; };
function Dog2(name) { Animal2.call(this, name); }
Dog2.prototype = Object.create(Animal2.prototype);
Dog2.prototype.constructor = Dog2;
Dog2.prototype.speak = function() { return this.name + ' barks'; };

自有属性与继承属性

const obj = { a: 1 };
const child = Object.create(obj);
child.b = 2;

console.log(Object.keys(child)); // ['b'] —— 仅自有可枚举属性
console.log('a' in child);        // true —— 检查整条原型链
console.log(child.hasOwnProperty('a')); // false

通过共享原型去重

方法只在原型上保存一份,由所有实例共享:

function User(name) { this.name = name; } // 构造函数每个实例都会执行
User.prototype.greet = function() {        // 所有实例共享同一个函数
  return `Hi, I'm ${this.name}`;
};

const a = new User('Alice');
const b = new User('Bob');
console.log(a.greet === b.greet); // true —— 同一个函数对象

现代替代方案:Object.create()

如果不需要构造函数,可以直接建立委托关系:

const logger = {
  log(msg) { console.log(`[LOG]: ${msg}`); },
};
const service = Object.create(logger);
service.run = function() { this.log('Running...'); };

service.run(); // "[LOG]: Running..."

常见误解

  • ES6 class 与原型完全不同。 实际上 class 仍然使用原型,类方法定义在 Class.prototype 上。
  • 修改原型只影响新对象。 [[Prototype]] 链接是实时的,向原型添加方法会立即对所有委托到它的对象可见。
  • this 指向定义方法的对象。 this 由函数调用方式决定;在原型方法中,它是点号前的接收者。

性能注意事项

每次属性访问都沿深层原型链查找会变慢。V8 会用隐藏类进行优化,但频繁调用 Object.setPrototypeOf() 会破坏这些优化。应使用扁平结构,并让对象在创建时拥有稳定的原型。

何时使用原型模式

  • 多个实例共享方法(类似 DOM 的层级结构)
  • 行为委托(作为 mixin 之外的对象组合方式)
  • 为内置 API 提供 polyfill 或扩展(需谨慎)

相关概念

  • 词法作用域:在函数定义时决定 this 的解析环境
  • 对象组合:优先委托状态、继承行为
  • Proxy:拦截对象上的所有属性访问