原型链本身不会循环,所谓“循环原型链”实为误称;真正存在的是循环引用,即对象间通过__proto__、属性或构造关系互相指向形成闭环,引擎检测到时会抛出rangeerror以防止无限递归。

原型链本身不会“循环”,所谓“循环原型链”其实是误称——真正存在的是循环引用,即两个或多个对象通过 __proto__、属性或构造关系互相指向,形成闭环。这种结构在 JavaScript 中虽不常见,但一旦出现,会干扰属性查找、深拷贝、序列化甚至触发运行时异常。
为什么不能有真正的循环原型链?
JavaScript 引擎在访问属性时沿 __proto__ 向上查找,若检测到重复访问同一原型(例如 A → B → C → A),多数现代引擎(V8、SpiderMonkey)会主动中断查找并抛出 RangeError: Maximum call stack size exceeded 或类似错误。这不是设计特性,而是防崩溃的保护机制。
换句话说:引擎不允许逻辑上无限递归的原型链;它会在检测到环时终止,而非静默执行。
哪些操作容易意外制造循环引用?
以下情况看似无害,实则可能埋下隐患:
- 手动篡改
__proto__:如obj.__proto__ = someOtherObj,而someOtherObj的原型链又回指obj - 使用非标准方式设置原型:比如用
Object.setPrototypeOf(obj, obj)或obj.__proto__ = obj - 深拷贝工具未处理原型:某些老旧的 clone 函数会把
constructor.prototype当作普通对象递归复制,导致新旧对象原型互引 - 反序列化恶意 JSON:含
{"__proto__": {"foo": "bar"}}的输入,若未经清洗就合并进对象,可能间接影响原型结构
如何安全检测和规避?
日常开发中无需主动构造循环原型链,重点是防御性检查与规范操作:
-
禁止直接赋值
__proto__:改用Object.setPrototypeOf(obj, proto),并在调用前校验proto !== obj && !isInPrototypeChain(obj, proto) -
深拷贝时跳过原型相关键:对
__proto__、constructor、prototype及其大小写变体一律忽略;优先使用structuredClone()(支持原型隔离) - 运行时监控原型完整性:可在启动阶段轻量扫描关键对象,用 Set 记录已访问原型,发现重复即告警(仅用于调试/测试环境)
-
冻结基础原型:在可信初始化阶段执行
Object.freeze(Object.prototype),阻止后续污染(注意兼容性,IE 不支持)
遇到循环引用该怎么处理?
如果已在调试中发现某对象原型链异常(例如 console.dir(obj) 显示 __proto__: [Circular]),可按步骤排查:
- 用
Object.getPrototypeOf(obj)逐层向上取原型,配合 Set 缓存,判断是否重复 - 检查是否用了第三方库的 merge/set 方法(如 lodash
- 审查所有用户输入参与的对象合并逻辑,强制过滤敏感键名
- 避免在生产环境使用
eval、Function构造函数或with,它们可能绕过常规防护











