直接给对象的__proto__赋值会触发引擎循环检测并抛出rangeerror,导致应用无响应;它绕过安全检查、禁用原型链缓存、使对象进入低效字典模式,必须禁用并改用object.setprototypeof()配合校验。

直接给对象的 __proto__ 赋值,尤其是赋成自身或已在其原型链中的对象,会立即触发 JavaScript 引擎的循环检测机制,抛出 RangeError: Maximum call stack size exceeded。这不是“卡死”,而是引擎主动中止执行——但若发生在关键路径(如初始化、渲染逻辑或事件回调中),确实会让整个应用无响应。真正危险的是它发生得悄无声息,且难以复现。
为什么__proto__ = obj是高危操作
这种写法绕过所有安全检查,直接篡改对象的内部 [[Prototype]] 槽位:
- V8 等引擎在属性访问时会缓存原型链路径,一旦发现重复原型(比如
A → B → A),立刻终止查找并报错 -
__proto__赋值不触发Object.defineProperty的校验逻辑,也不受Object.preventExtensions()限制(仅在严格模式下静默失败) - 它还会让 V8 将对象标记为“字典模式”,后续所有属性访问变慢,影响性能
必须禁用的写法与替代方案
以下代码一律禁止出现在生产环境:
-
obj.__proto__ = obj(自引用) -
obj.__proto__ = otherObj(未校验otherObj是否已含obj) -
child.__proto__ = parent.prototype(应改用Object.create(parent.prototype))
统一改用 Object.setPrototypeOf(),并在调用前加校验:
function safeSetPrototypeOf(obj, proto) {
if (obj === proto) return false;
let p = proto;
while (p !== null) {
if (p === obj) return false;
p = Object.getPrototypeOf(p);
}
Object.setPrototypeOf(obj, proto);
return true;
}
从源头切断污染入口
很多循环引用不是主动写的,而是由外部输入间接引入:
- 反序列化 JSON 后,不要直接
Object.assign(target, parsed);先递归过滤掉所有键名为__proto__、constructor、prototype的字段 - 第三方库合并配置时(如
lodash.merge),确保版本 ≥ 4.17.21,并在传入前做白名单校验 - 纯数据容器一律用
Object.create(null)创建,它没有__proto__,天然免疫此类问题
上线前必须做的防护动作
防御性冻结比事后排查更有效:
- 在应用最开始(第一个 script 标签内)执行:
Object.freeze(Object.prototype),阻止任何后续对原生原型的修改 - 对核心配置对象、全局状态管理器,在初始化后立即调用
Object.preventExtensions() - 开发阶段启用 ESLint 规则
no-proto和no-prototype-builtins,强制拦截相关写法
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











