返回列表
Q1:为什么
Q2:
技术
65 分钟
原型链与对象血缘关系
Site Owner
发布于 2026-05-21
系统讲解原型链查找与截断机制、构造函数与原型的三角关系、继承模式演进、class语法糖的完整编译产物及Object.create(null)的性能优势

原型链与对象血缘关系
JavaScript 的继承模型基于原型链(Prototype Chain),而非传统的类继承。每个对象都有一个内部槽 [[Prototype]],指向其原型对象,形成链式查找路径。理解原型链是理解 class 语法糖、instanceof 运算符、属性查找算法和性能优化的基础。V8 通过隐藏类(Hidden Classes/Maps)和内联缓存(Inline Caches)优化了原型链查找,使其在大多数场景下接近 O(1) 复杂度。
目录
- [[Prototype]] 内部槽与 proto
- prototype 属性与构造函数
- 原型链查找算法
- Object.create 与纯净原型
- class 语法糖的原型本质
- instanceof 与 Symbol.hasInstance
- 属性遮蔽与原型污染
- 实战案例
- 深度追问
- 总结表格
1. [[Prototype]] 内部槽与 proto
每个普通对象都有 [[Prototype]] 内部槽(规范 §10.1):
// 代码示例 1:原型访问的三种方式
const obj = { x: 1 };
// 方式1:Object.getPrototypeOf(推荐)
console.log(Object.getPrototypeOf(obj) === Object.prototype); // true
// 方式2:__proto__(Annex B,不推荐用于生产)
console.log(obj.__proto__ === Object.prototype); // true
// 方式3:Reflect.getPrototypeOf
console.log(Reflect.getPrototypeOf(obj) === Object.prototype); // true
原型链的终点
// 原型链终止于 null
console.log(Object.getPrototypeOf(Object.prototype)); // null
// 完整的原型链示例
const arr = [1, 2, 3];
// arr → Array.prototype → Object.prototype → null
console.log(Object.getPrototypeOf(arr) === Array.prototype); // true
console.log(Object.getPrototypeOf(Array.prototype) === Object.prototype); // true
console.log(Object.getPrototypeOf(Object.prototype) === null); // true
2. prototype 属性与构造函数
// 代码示例 2:constructor 与 prototype 的关系
function Person(name) {
this.name = name;
}
// 函数的 prototype 属性
console.log(typeof Person.prototype); // 'object'
console.log(Person.prototype.constructor === Person); // true
// 实例的 [[Prototype]] 指向构造函数的 prototype
const alice = new Person('Alice');
console.log(Object.getPrototypeOf(alice) === Person.prototype); // true
console.log(alice.constructor === Person); // true(通过原型链查找)
constructor 属性的脆弱性
// 代码示例 3:覆盖 prototype 导致 constructor 丢失
function Animal(type) {
this.type = type;
}
// 直接替换 prototype(常见错误)
Animal.prototype = {
speak() { return `I am a ${this.type}`; }
};
const dog = new Animal('dog');
console.log(dog.constructor === Animal); // false!
console.log(dog.constructor === Object); // true(来自 Object.prototype)
// 修复:显式设置 constructor
Animal.prototype = {
constructor: Animal, // 手动恢复
speak() { return `I am a ${this.type}`; }
};
// 或使用 Object.defineProperty 使其不可枚举
Object.defineProperty(Animal.prototype, 'constructor', {
value: Animal,
enumerable: false,
writable: true,
configurable: true
});
3. 原型链查找算法
ECMAScript §10.1.8 定义了 [[Get]] 操作的查找过程:
// 代码示例 4:原型链查找过程可视化
function lookup(obj, prop) {
let current = obj;
while (current !== null) {
if (Object.prototype.hasOwnProperty.call(current, prop)) {
return { found: true, value: current[prop], owner: current };
}
current = Object.getPrototypeOf(current);
}
return { found: false, value: undefined };
}
// 示例
const grandparent = { family: 'Smith', legacy: 'old' };
const parent = Object.create(grandparent);
parent.generation = 2;
parent.legacy = 'updated'; // 遮蔽
const child = Object.create(parent);
child.name = 'Bob';
console.log(lookup(child, 'name')); // { found: true, owner: child }
console.log(lookup(child, 'generation')); // { found: true, owner: parent }
console.log(lookup(child, 'family')); // { found: true, owner: grandparent }
console.log(lookup(child, 'legacy')); // { found: true, owner: parent }(遮蔽)
console.log(lookup(child, 'missing')); // { found: false }
V8 的隐藏类优化
// V8 使用 Hidden Classes (Maps) 优化属性访问
// 相同结构的对象共享 Hidden Class
function Point(x, y) {
this.x = x; // Transition: Map0 → Map1
this.y = y; // Transition: Map1 → Map2
}
// 这两个对象共享同一个 Hidden Class (Map2)
const p1 = new Point(1, 2);
const p2 = new Point(3, 4);
// ⚠️ 以下代码会导致 Hidden Class 分裂
const p3 = new Point(5, 6);
p3.z = 7; // p3 获得新的 Hidden Class (Map3)
4. Object.create 与纯净原型
// 代码示例 5:Object.create 的强大用法
// 创建无原型对象(纯净字典)
const dict = Object.create(null);
dict.key = 'value';
console.log(dict.toString); // undefined(无原型污染)
console.log('key' in dict); // true
// 创建带属性描述符的对象
const immutable = Object.create(Object.prototype, {
id: { value: 1, writable: false, enumerable: true, configurable: false },
name: { value: 'fixed', writable: false, enumerable: true, configurable: false }
});
immutable.id = 2; // 静默失败(严格模式下 TypeError)
console.log(immutable.id); // 1
Object.create(null) 的实际应用
// 用作安全的哈希表(避免 __proto__ 注入攻击)
function createSafeCache() {
const cache = Object.create(null);
return {
get(key) { return cache[key]; },
set(key, value) { cache[key] = value; },
has(key) { return key in cache; },
delete(key) { delete cache[key]; }
};
}
const cache = createSafeCache();
cache.set('__proto__', 'safe'); // 不会污染原型
cache.set('constructor', 'safe'); // 不会影响构造函数
5. class 语法糖的原型本质
// 代码示例 6:class 的等价原型写法
class Animal {
constructor(name) {
this.name = name;
}
speak() {
return `${this.name} makes a sound.`;
}
static create(name) {
return new this(name);
}
}
class Dog extends Animal {
constructor(name) {
super(name);
}
speak() {
return `${this.name} barks.`;
}
}
// 等价的 ES5 写法
function AnimalES5(name) {
this.name = name;
}
AnimalES5.prototype.speak = function() {
return `${this.name} makes a sound.`;
};
AnimalES5.create = function(name) {
return new this(name);
};
function DogES5(name) {
AnimalES5.call(this, name); // super(name)
}
DogES5.prototype = Object.create(AnimalES5.prototype);
DogES5.prototype.constructor = DogES5;
Object.setPrototypeOf(DogES5, AnimalES5); // 静态方法继承
DogES5.prototype.speak = function() {
return `${this.name} barks.`;
};
验证 class 的原型结构
// 代码示例 7:验证继承链
const rex = new Dog('Rex');
console.log(rex instanceof Dog); // true
console.log(rex instanceof Animal); // true
// 原型链:rex → Dog.prototype → Animal.prototype → Object.prototype → null
console.log(Object.getPrototypeOf(rex) === Dog.prototype); // true
console.log(Object.getPrototypeOf(Dog.prototype) === Animal.prototype); // true
console.log(Object.getPrototypeOf(Animal.prototype) === Object.prototype); // true
// 构造函数链(静态继承)
console.log(Object.getPrototypeOf(Dog) === Animal); // true
console.log(Object.getPrototypeOf(Animal) === Function.prototype); // true
6. instanceof 与 Symbol.hasInstance
// 代码示例 8:instanceof 的底层机制
// instanceof 检查右侧的 prototype 是否在左侧的原型链上
function myInstanceof(obj, Constructor) {
let proto = Object.getPrototypeOf(obj);
const target = Constructor.prototype;
while (proto !== null) {
if (proto === target) return true;
proto = Object.getPrototypeOf(proto);
}
return false;
}
// 自定义 instanceof 行为
class EvenNumber {
static [Symbol.hasInstance](instance) {
return typeof instance === 'number' && instance % 2 === 0;
}
}
console.log(2 instanceof EvenNumber); // true
console.log(3 instanceof EvenNumber); // false
console.log(4 instanceof EvenNumber); // true
7. 属性遮蔽与原型污染
属性遮蔽的三种情况
// 代码示例 9:属性遮蔽的复杂情况
const proto = {};
Object.defineProperty(proto, 'readOnly', {
value: 'original',
writable: false // 只读
});
const child = Object.create(proto);
// 情况1:正常属性 → 创建遮蔽属性
// 情况2:原型链上存在 writable:false → 严格模式 TypeError,非严格静默失败
// 情况3:原型链上存在 setter → 调用 setter,不创建遮蔽
child.readOnly = 'modified'; // 非严格模式:静默失败!
console.log(child.readOnly); // 'original'
console.log(child.hasOwnProperty('readOnly')); // false!
原型污染攻击
// ⚠️ 危险示例 — 原型污染
function vulnerableMerge(target, source) {
for (const key in source) {
if (typeof source[key] === 'object' && source[key] !== null) {
target[key] = target[key] || {};
vulnerableMerge(target[key], source[key]);
} else {
target[key] = source[key];
}
}
}
// 攻击 payload
const malicious = JSON.parse('{"__proto__": {"isAdmin": true}}');
vulnerableMerge({}, malicious);
console.log(({}).isAdmin); // true! 所有对象都被污染
// 安全版本
function safeMerge(target, source) {
for (const key of Object.keys(source)) { // 使用 Object.keys 而非 for-in
if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
continue; // 跳过危险键
}
if (typeof source[key] === 'object' && source[key] !== null) {
target[key] = target[key] || {};
safeMerge(target[key], source[key]);
} else {
target[key] = source[key];
}
}
}
8. 实战案例
实战案例 1:高效的继承模式 — 组合寄生继承
function inheritPrototype(SubType, SuperType) {
const proto = Object.create(SuperType.prototype);
Object.defineProperty(proto, 'constructor', {
value: SubType,
enumerable: false,
writable: true,
configurable: true
});
SubType.prototype = proto;
// 继承静态方法
Object.setPrototypeOf(SubType, SuperType);
}
function EventEmitter() {
this._events = Object.create(null);
}
EventEmitter.prototype.on = function(event, listener) {
(this._events[event] || (this._events[event] = [])).push(listener);
return this;
};
EventEmitter.prototype.emit = function(event, ...args) {
const listeners = this._events[event] || [];
listeners.forEach(fn => fn.apply(this, args));
};
function Server(port) {
EventEmitter.call(this);
this.port = port;
}
inheritPrototype(Server, EventEmitter);
Server.prototype.start = function() {
this.emit('start', this.port);
};
实战案例 2:Mixin 模式 — 多继承的替代方案
// Mixin 工厂
const Serializable = (Base) => class extends Base {
serialize() {
return JSON.stringify(this);
}
static deserialize(json) {
return Object.assign(new this(), JSON.parse(json));
}
};
const Validatable = (Base) => class extends Base {
validate() {
const rules = this.constructor.validationRules || {};
const errors = [];
for (const [field, rule] of Object.entries(rules)) {
if (!rule(this[field])) {
errors.push(`Invalid ${field}: ${this[field]}`);
}
}
return errors.length === 0 ? { valid: true } : { valid: false, errors };
}
};
// 使用 Mixin
class User extends Serializable(Validatable(class {})) {
static validationRules = {
name: (v) => typeof v === 'string' && v.length > 0,
age: (v) => typeof v === 'number' && v >= 0
};
constructor(name, age) {
super();
this.name = name;
this.age = age;
}
}
const user = new User('Alice', 30);
console.log(user.validate()); // { valid: true }
console.log(user.serialize()); // '{"name":"Alice","age":30}'
实战案例 3:性能优化 — 避免原型链过长
// ❌ 反模式:过长的原型链
class A { methodA() {} }
class B extends A { methodB() {} }
class C extends B { methodC() {} }
class D extends C { methodD() {} }
class E extends D { methodE() {} }
// 查找 methodA 需要遍历 4 层原型链
// ✅ 优化:扁平化 + 组合
class OptimizedE {
constructor() {
// 将常用方法直接定义在实例上(牺牲内存换取速度)
this.methodA = () => { /* ... */ };
}
methodB() {}
methodC() {}
methodD() {}
methodE() {}
}
// V8 内联缓存(IC)对于短原型链效率更高
// 建议:保持原型链深度 ≤ 3-4 层
9. 深度追问
Q1:为什么 Object.setPrototypeOf 被认为是性能杀手?
修改对象的 [[Prototype]] 会使 V8 的隐藏类(Map)失效,导致所有与该对象相关的内联缓存(IC)需要重新计算。V8 甚至会将受影响的函数从优化代码降级回解释执行。建议在对象创建时通过 Object.create 设定原型,而非后期修改。
Q2:class 中的私有字段如何影响原型继承?
私有字段(#field)存储在实例上,通过 WeakMap 语义实现(V8 实际使用 Brand Check)。它们不参与原型链查找,子类无法访问父类的私有字段,即使通过 super 也不行。
Q3:如何安全地检测一个属性是否来自原型链?
// 推荐方式
Object.prototype.hasOwnProperty.call(obj, 'prop');
// 或 ES2022+
Object.hasOwn(obj, 'prop');
10. 总结表格
| 概念 | 描述 | 访问方式 |
|---|---|---|
[[Prototype]] | 对象的内部原型槽 | Object.getPrototypeOf() |
__proto__ | 访问器属性(Annex B) | obj.__proto__ |
.prototype | 函数的 prototype 属性 | Fn.prototype |
constructor | 指回构造函数 | obj.constructor |
| 创建方式 | 原型设置 | 使用场景 |
|---|---|---|
new Fn() | Fn.prototype | 构造函数模式 |
Object.create(proto) | 指定 proto | 纯原型继承 |
class extends | 自动设置两条链 | 现代继承 |
{} 字面量 | Object.prototype | 普通对象 |
Object.create(null) | null | 纯净字典 |
| 性能建议 | 原因 |
|---|---|
避免 Object.setPrototypeOf | 破坏隐藏类优化 |
| 保持原型链 ≤ 4 层 | 查找深度影响 IC 效率 |
| 同形对象共享结构 | Hidden Class 复用 |
| 避免运行时增删原型方法 | Map 转换开销 |
下一篇:05. 闭包的内存模型与生命周期