什么是原型继承
原型继承是指对象可以把属性和方法访问委托给另一个对象(它的原型)。每个 JavaScript 对象都有一个内部的 [[Prototype]] 链接(可通过 Object.getPrototypeOf() 读取,旧式写法是 __proto__)。当访问对象自身不存在的属性时,引擎会沿着原型链向上查找,直到找到该属性或到达 null。
这与基于类的继承不同:JavaScript 没有类蓝图,对象直接继承自其他对象。ES6 的 class 语法本质上是对原型机制的语法封装。
原型链
当你访问 obj.prop 时:
- 检查
prop是否直接存在于obj上(自有属性)。 - 如果不存在,检查
obj.[[Prototype]]上是否有prop。 - 继续向上查找,直到找到属性或链结束于
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:拦截对象上的所有属性访问