__proto__ 是特殊属性,赋值会修改对象原型而非添加自有属性,导致原型链异常;非对象值可能被忽略或报错,且 obj["__proto__"] = value 在旧环境中行为不一致。

当对象属性名是 __proto__ 时,它不会被当作普通字符串键处理,而是触发 JavaScript 引擎对原型链的特殊干预——这会导致赋值、枚举、继承行为全部失常,是典型的“看似合法、实则危险”的陷阱。
__proto__ 属性会覆盖原型链,而不是存为普通字段
给对象直接设置 obj.__proto__ = value,不是添加一个叫 "__proto__" 的自有属性,而是尝试修改该对象的内部 [[Prototype]] 槽。如果 value 是对象,就真的替换了原型;如果是非对象(如字符串、null),多数引擎会静默忽略或抛错。更隐蔽的是:obj["__proto__"] = value 在某些旧环境(如 Chrome
- 用
Object.getOwnPropertyNames(obj)查不到它——因为它不是“自有属性”,而是原型操作副作用 - 用
JSON.stringify(obj)会跳过它,但for...in可能意外遍历到原型上的同名属性 - 若
value是对象,后续所有继承自该对象的方法调用都可能失效(比如obj.toString()报错)
枚举和检测时容易漏掉或误判
Object.keys(obj) 和 for...in 都不返回 "__proto__" 键,因为引擎把它剥离出属性空间;但 Object.getOwnPropertyNames(Object.prototype) 却可能显示它——如果污染已发生在原型上。这意味着:你以为对象“干净”,其实原型已被篡改。
- 检查自有属性时,别只依赖
Object.keys(),必须搭配Object.getOwnPropertyNames()和Object.getPrototypeOf(obj)分层查看 - 写白名单校验时,正则
/^__proto__$/.test(key)要放在最前——哪怕 key 看似在Object.keys()里没出现,也要拦截赋值源头 - 用
console.dir(obj)在 DevTools 中展开时,__proto__显示在“原型”面板下,而非“自身属性”,视觉上就暗示它不属于数据层
安全写入和替代方案
如果你确实需要存储一个名为 "__proto__" 的配置项(比如后端返回的字段名无法更改),绝不能直接用点号或方括号赋值,而要绕过引擎识别:
- 用动态键名:
obj[("__proto__")] = value—— 加括号不会改变语义,但部分工具链会更谨慎解析 - 改用带前缀的键:如
obj["_proto_"]或obj["$$proto"],服务端/客户端约定统一映射 - 用
Object.defineProperty强制设为不可枚举、不可配置:Object.defineProperty(obj, "__proto__", { value, enumerable: false, configurable: false }),此时它才真正成为“普通属性” - 读取时统一走
Object.getOwnPropertyDescriptor(obj, "__proto__"),避免触发原型访问
运行时防御建议
在关键入口(如 API 响应解析、表单提交、深合并前)加入轻量防护:
- 冻结原型链:
Object.freeze(Object.prototype)(现代浏览器支持,IE 不兼容) - 封装安全合并函数:
safeAssign(target, source),内部过滤掉"__proto__"、"constructor"、"prototype"等敏感键 - 开发期启用 Chrome DevTools 的 “Break on property access” 功能,对
__proto__设断点,快速定位谁在偷偷修改

















