__proto__ 不该被 object.defineproperty 覆盖,因其是访问器属性,强行覆盖会将其变为数据属性,导致 [[prototype]] 未更新、原型链断裂、instanceof 失效及方法查找异常。

__proto__ 被 Object.defineProperty 强行覆盖,不是“改了个属性”,而是直接篡改了对象的内部 [[Prototype]] 链接,导致该对象与原本继承体系彻底脱钩。这种操作在绝大多数场景下既不必要,也不安全,极易引发难以排查的原型链断裂事故。
__proto__ 本不该被 defineProperty 覆盖
__proto__ 是一个访问器属性(accessor property),由引擎原生提供,其 getter/setter 负责读写对象的 [[Prototype]] 内部槽位。标准行为中:
-
obj.__proto__ = X实际调用的是 setter,会校验X是否为对象或null,并安全更新[[Prototype]] - 但若用
Object.defineProperty(obj, '__proto__', { value: X }),就绕过了 setter 逻辑,强行把__proto__变成一个数据属性(data property),值为X,且不可枚举、不可配置(取决于 descriptor)
后果是:
✅ obj.__proto__ 还能读出 X(因为现在它真是个自有属性)
❌ obj 的 [[Prototype]] 并未改变,仍指向原来的原型
❌ 后续通过 obj.__proto__ = Y 尝试修改,将完全失效(因为 __proto__ 已不是访问器)
❌ obj instanceof SomeClass、obj.constructor、方法查找等全部按旧 [[Prototype]] 行为,与 __proto__ 显示值矛盾
举例:
const a = {}; Object.defineProperty(a, '__proto__', { value: Array.prototype }); console.log(a.__proto__); // Array.prototype(显示值) console.log(Object.getPrototypeOf(a)); // Object.prototype(真实[[Prototype]]) console.log(a instanceof Array); // false(按真实链判断)
断裂事故的典型表现
- 方法调用失败:
a.push(1)报错a.push is not a function(因a实际没继承Array.prototype) -
instanceof判断失灵:明明想让对象“假装”是某类实例,结果全为false -
constructor错乱:a.constructor仍指向Object,与__proto__显示值不一致 - 继承链查找中断:
a.toString()仍走Object.prototype.toString,而非你设的Array.prototype.toString
如何修复已发生的覆盖
不能靠“再 define 一次”来还原,必须分两步:
-
恢复
__proto__的访问器本质(前提是 descriptor 允许):Object.defineProperty(obj, '__proto__', { get() { return Object.getPrototypeOf(this); }, set(v) { Object.setPrototypeOf(this, v); }, enumerable: true, configurable: true }); -
同步修正真实
[[Prototype]](更关键):Object.setPrototypeOf(obj, desiredPrototype);
注意:若原 descriptor 中
configurable: false,则无法重新定义__proto__访问器,只能接受它作为死数据属性——此时唯一可靠路径是重建对象或手动挂载方法。
正确替代方案:别碰 __proto__,用标准 API
| 错误做法 | 正确做法 | 说明 |
|---|---|---|
Object.defineProperty(obj, '__proto__', {...}) |
Object.setPrototypeOf(obj, proto) |
直接设置 [[Prototype]],语义清晰、可逆、兼容性好 |
obj.__proto__ = X(虽可用但非标准) |
Object.create(X) 或 Object.setPrototypeOf({}, X)
|
创建新对象时指定原型,避免污染已有实例 |
| 想让对象“拥有某类方法” | 组合或代理:const arrLike = { ...obj, push, pop }
|
不依赖原型链,更可控 |
本质上,__proto__ 是调试和兼容层接口,不是设计给运行时动态重定义的字段。强行用 defineProperty 覆盖它,等于拆掉汽车的方向盘去拧转向柱——看起来动了,实际已失去控制。











