不能将__proto__作为普通属性名使用,因其会被引擎识别为修改对象[[prototype]]的指令,导致原型被意外替换、方法丢失、枚举异常;安全做法是加括号动态键、加前缀重命名或用object.defineproperty强制设为不可枚举自有属性。

直接避免——别用 __proto__ 作为属性名写入对象。它不是普通字段,而是 JavaScript 引擎对原型链的控制开关,任何看似“赋值”的操作,实际都在修改对象的继承关系,极易引发静默失效、方法丢失、枚举异常等隐蔽故障。
为什么不能当普通字段用
引擎会把 obj.__proto__ = value 或 obj["__proto__"] = value 当作修改 [[Prototype]] 的指令,而不是添加一个字符串键:
- 若
value是对象,该对象立刻成为obj的新原型,原有方法(如toString、hasOwnProperty)可能消失 - 若
value是null、undefined或原始值,多数环境静默忽略,但部分旧版本(如早期 Chrome)会抛错 -
Object.keys(obj)、for...in都不返回"__proto__",因为它根本不在自有属性中;但console.dir(obj)会在“原型”面板下显示它,视觉上就暴露了问题本质
如何安全地存一个叫 "__proto__" 的数据
当后端返回字段名恰好是 "__proto__"(比如配置项、日志字段),你无法改名,但必须绕过引擎识别:
- 用带括号的动态键:
obj[("__proto__")] = value—— 语义不变,但某些 lint 工具和 IDE 更倾向将其视为普通字符串键 - 统一加前缀或后缀:
obj["_proto_"]或obj["$$proto"],前后端约定映射规则,彻底脱离保留字范畴 - 强制定义为不可枚举的自有属性:
Object.defineProperty(obj, "__proto__", { value, enumerable: false, configurable: false })—— 此时它才真正作为数据字段存在,不会触发原型变更
检查与拦截:从源头堵住风险
仅靠开发自觉不够,需在工具链中嵌入防御机制:
- 白名单校验逻辑中,把
/^__proto__$/.test(key)放在最前面,一旦匹配立即拒绝或重命名 - CI 流程中加入 ESLint 规则
no-prototype-builtins和自定义规则,禁止.__proto__赋值、禁止字符串字面量直接用于对象键名匹配 - 调试时用
Object.getPrototypeOf(obj)和Object.getOwnPropertyNames(obj)分层查看,确认“属性”和“原型”边界清晰,避免误判











