原型链本身不会循环,javascript引擎(如v8)检测到重复原型时主动抛出rangeerror以防护;所谓“循环原型链”实为人为造成的循环引用,源于__proto__误赋、object.setprototypeof未校验、老旧深拷贝或恶意json反序列化,会中断属性查找、阻断深拷贝、引发错误并导致原型污染。

原型链本身不会形成真正的循环,JavaScript 引擎(如 V8)会在属性查找过程中主动检测重复原型并抛出 RangeError: Maximum call stack size exceeded,这是保护机制,不是允许的行为。所谓“循环原型链”,实际是人为造成的循环引用——比如通过 __proto__、Object.setPrototypeOf() 或反序列化操作,让对象 A 的原型链中意外包含自身或闭环结构。它会中断属性访问、阻断深拷贝、引发运行时错误,甚至打开原型污染入口。
哪些操作容易悄悄引入循环引用
看似安全的写法,可能埋下隐患:
- 直接赋值
obj.__proto__ = obj或obj.__proto__ = otherObj,而otherObj的原型链已包含obj - 调用
Object.setPrototypeOf(obj, obj)或未校验目标原型是否已在源对象链中 - 用老旧深拷贝工具(非
structuredClone)递归复制时,把constructor.prototype当普通对象处理,导致新旧对象原型互指 - 反序列化用户输入的 JSON(如
{"__proto__": {"admin": true}}),未经清洗就合并进现有对象
如何主动检测潜在环
不依赖报错后再排查,可在关键初始化阶段做轻量扫描:
- 用
Set缓存已访问原型,从目标对象开始反复调用Object.getPrototypeOf() - 一旦发现某个原型已被记录,即定位到环的起点
- 仅在测试环境启用该逻辑,避免生产环境性能开销
- 配合
console.dir(obj)观察控制台是否显示[Circular]提示
日常开发中的防御性实践
预防远比修复重要,关键是切断人为干预路径:
- 禁用直接写
__proto__,统一使用Object.setPrototypeOf(),且调用前必须校验:proto !== obj && !isInPrototypeChain(obj, proto) - 深拷贝时显式跳过所有原型相关键:忽略
__proto__、constructor、prototype及其大小写变体 - 优先采用原生
structuredClone(),它默认隔离原型,不复制原型链 - 在可信初始化完成后执行
Object.freeze(Object.prototype),防止后续原型污染(注意 IE 不兼容)
发现问题后怎么快速定位
若已出现异常(如属性访问失败、深拷贝卡死、控制台报错),按此顺序排查:
- 手动遍历原型链:
let p = obj; const seen = new Set(); while (p) { if (seen.has(p)) break; seen.add(p); p = Object.getPrototypeOf(p); } - 检查是否用了
lodash.merge、_.set等第三方工具,确认所有用户输入都经过白名单过滤 - 审查自定义的原型设置封装函数,尤其关注动态构造类、运行时 patch 构造器等高风险场景
- 查看是否有服务端返回的 JSON 被直接
Object.assign()到本地对象上











