直接修改 __proto__ 不能深入探究原型链寻址机制,反而掩盖真实行为、引发性能问题且已被标准弃用;应通过 Object.getPrototypeOf、accessor、Proxy 等标准方式理解静态查找规则与属性拦截。

直接修改 __proto__ 并不能“深入探究”原型链寻址机制,反而会掩盖其真实行为,且在现代 JavaScript 实践中属于不推荐、非标准、已弃用的操作方式。
__proto__ 是早期浏览器为调试和实验提供的非标准属性,它暴露了对象内部的 [[Prototype]] 链接,但它的可写性带来的是不可控的副作用,而非教学价值。真正理解原型链寻址与属性拦截,应聚焦于引擎如何按规则查找、何时停止、以及什么能被拦截——这些都与 __proto__ 的手动赋值无关。
原型链寻址的核心是静态路径 + 确定性规则
JavaScript 引擎在访问 obj.prop 时,执行的是严格单向查找:
- 先查
obj自身的自有属性(包括数据属性和 accessor) - 若无,跳转至
obj.[[Prototype]](即obj.__proto__指向的对象) - 继续查该对象自身,再跳
.[[Prototype]],逐层向上 - 直到抵达
Object.prototype,其[[Prototype]]为null,查找终止,返回undefined
这个过程不依赖 __proto__ 是否被改过,而依赖每个对象创建时确立的 [[Prototype]] 内部槽位。你用 obj.__proto__ = X 修改,只是替换了那个槽位的值,但引擎仍按同样规则走新链——这无法揭示“机制”,只改变了输入条件。
立即学习“Java免费学习笔记(深入)”;
属性拦截的关键不在 __proto__,而在 get / set 与 has
真正实现属性拦截的机制是:
-
accessor 属性:在对象或原型上定义
{ get() { ... }, set(v) { ... } },访问时自动触发 -
Proxy:通过
new Proxy(obj, { get(target, key) { ... } })拦截所有读取,无论属性是否存在、在哪一层 - Object.defineProperty + configurable: false:控制属性是否可被删除或重定义,影响遮蔽(shadowing)行为
例如:
const proto = {
get name() { console.log('intercepted via getter'); return 'default'; }
};
const obj = {};
Object.setPrototypeOf(obj, proto);
console.log(obj.name); // 触发 getter,输出日志并返回 'default'这里起作用的是 get,不是 __proto__ 的赋值动作。
修改 __proto__ 的实际问题远多于启发
- V8 等引擎会对原型链稳定的对象做内联缓存(IC)优化;动态改
__proto__会强制去优化,大幅拖慢后续所有属性访问 - 在严格模式下对不可扩展/冻结对象操作
__proto__会抛错;非严格模式下则静默失败,行为不可预测 -
__proto__已被 ECMAScript 规范标记为“deprecated”,现代代码应使用Object.getPrototypeOf()和Object.setPrototypeOf()(仅限必要场景)
真正有效的探究方式
想看清原型链如何工作,推荐以下安全、标准、可复现的方法:
- 使用
Object.getPrototypeOf(obj)逐级打印原型链,观察终点是否为null - 在各层原型上添加
console.log的 accessor,验证查找顺序 - 用
Object.getOwnPropertyDescriptor()查看属性是否为value或get,确认是否触发拦截 - 创建
Proxy包裹对象,统一拦截get/set/has,观察每次访问的实际目标层级
比如:
const A = { a: 1 };
const B = { b: 2 };
Object.setPrototypeOf(B, A);
const p = new Proxy(B, {
get(target, key) {
console.log('proxy get:', key, 'on', target === B ? 'B' : 'A');
return Reflect.get(...arguments);
}
});
p.a; // 输出 "proxy get: a on A",说明查到了原型 A不复杂但容易忽略。


















