原型链本身不会循环,因引擎会检测并抛出rangeerror;循环引用由手动误操作导致,会中断属性查找、阻断深拷贝、引发错误及原型污染风险。

原型继承中不会自然形成循环引用,但手动操作可能意外引入。关键不是“修复循环”,而是预防误设和及时拦截。
为什么原型链本身不能循环
JavaScript 引擎(如 V8)在沿 __proto__ 向上查找属性时,会内部记录已访问的原型对象。一旦发现重复路径(比如 A → B → C → A),立即抛出 RangeError: Maximum call stack size exceeded。这不是 bug,而是引擎的主动保护机制——原型链设计为单向委托链,终点必须是 null。
哪些操作容易触发循环
- 直接赋值 obj.__proto__ = obj 或 obj.__proto__ = otherObj,而 otherObj 的原型链中又包含 obj
- 用 Object.setPrototypeOf(obj, someObj) 未校验目标是否已在 obj 的原型链中
- 反序列化含 {"__proto__": {...}} 的 JSON 并合并进现有对象,且未过滤
- 动态构造原型时,把当前实例或其父级误设为子类的 constructor.prototype
安全检测与规避方法
日常开发应以预防为主:
- 禁用直接写 __proto__,统一用 Object.setPrototypeOf(),并在调用前检查:proto !== obj && !isInPrototypeChain(obj, proto)
- 深拷贝时跳过所有原型相关键:显式忽略 __proto__、constructor、prototype 及其大小写变体;优先使用原生 structuredClone()(支持原型隔离)
- 启动阶段轻量扫描关键对象:用 Set 缓存已访问原型,逐层调用 Object.getPrototypeOf(),发现重复即告警(仅用于测试环境)
- 在可信初始化后冻结基础原型:Object.freeze(Object.prototype),防止后续污染(注意 IE 不兼容)
发现异常后的排查步骤
若控制台报错或属性访问异常,可按此流程定位:
- 用 Object.getPrototypeOf(obj) 逐层向上取原型,配合 Set 记录路径,首次重复即为环起点
- 检查是否使用了第三方 merge/set 工具(如 lodash 的 _.merge),确认所有用户输入都经过白名单过滤
- 审查涉及 __proto__ 或 setPrototypeOf 的自定义封装逻辑,尤其关注动态构造原型的场景











