判断原型链是否被修改的关键是建立可信基准并比对现状,通过快照关键原型属性、检测非预期属性注入、验证链完整性与冻结状态,并优先采用冻结、proxy监控等预防措施。

判断一个对象的原型链是否被修改,本质上不是“回溯历史”,而是检查当前状态是否偏离了预期的初始结构。JavaScript 没有内置的版本记录机制,所以关键在于建立可信基准并比对现状。
建立并比对原型快照
最实用的方式是在应用初始化早期(第三方库加载前)保存关键原型的属性快照,后续对比即可发现新增、删除或重定义的属性。
- 用 Object.getOwnPropertyNames() 和 Object.getOwnPropertyDescriptors() 获取原型上所有自有属性及其配置(
value、writable、enumerable、configurable) - 对
Object.prototype、Array.prototype等敏感原型做快照,例如:const baseArrayProto = Object.getOwnPropertyDescriptors(Array.prototype); - 运行时调用相同方法获取当前状态,逐项比对 descriptor 是否一致(注意函数引用无法深比较,可比对
toString()或使用===判断是否为原始内置方法)
检测非预期属性注入
很多原型污染攻击或误操作会向原型添加新属性(如 __proto__.admin = true),可通过遍历检查是否有不该存在的键。
- 遍历
Object.getOwnPropertyNames(targetPrototype),过滤掉已知合法属性(如push、toString等) - 特别留意常见污染键名:
constructor、__proto__、prototype、toString被重写,或明显业务相关的非常规名称(如isAdmin、hack) - 对
Object.prototype尤其谨慎——它影响所有普通对象,新增属性会出现在几乎所有for...in循环中
验证原型链完整性与冻结状态
修改不只体现在新增属性,还可能破坏链结构或改变可变性。检查这些状态能间接反映是否被干预。
- 用 Object.getPrototypeOf() 沿链向上走,确认每层是否仍指向预期对象(比如
Array.prototype的原型是否仍是Object.prototype) - 检查关键原型是否被 Object.freeze() 或 Object.seal():调用
Object.isFrozen(proto)或Object.isSealed(proto) - 尝试安全地写入测试属性(如用 Symbol 作唯一 key),捕获
TypeError—— 若失败,说明已被冻结;若成功但后续消失,可能被拦截或代理劫持
预防优于检测
真正可靠的方案不是等修改发生后再判断,而是从源头限制修改能力。
- 在构建阶段用 ESLint 规则(如
no-extend-native)禁止扩展原生原型 - 对核心原型调用 Object.freeze()(注意:需在所有代码执行前完成,且不能冻结已被污染的对象)
- 使用 Proxy 包裹自定义构造函数的 prototype,监控
set、defineProperty等操作并告警 - 避免使用
__proto__赋值或Object.setPrototypeOf(),它们是原型链被动态篡改的主要入口











