最根本的防范方式是用 object.create(prototype) 显式指定原型,用 object.create(null) 切断原型链;关键在于明确设谁、如何用,避免隐式继承 object.prototype 引入 proto 等污染通道。

直接用 Object.create(prototype) 指定原型对象,同时用 Object.create(null) 切断原型链起点——这是防范 __proto__ 污染最根本的方式。关键不在“怎么设”,而在“设谁”和“设完怎么用”。
明确指定原型对象,不依赖默认继承
普通写法如 {} 或 new Object() 都隐式以 Object.prototype 为原型,给 __proto__、constructor 等键留下注入通道。而 Object.create 让你显式控制原型:
- 传入真实原型对象:
const child = Object.create(parent);,此时child.__proto__ === parent,干净可控 - 若只需数据容器(如路由表、配置缓存),直接传
null:const dict = Object.create(null);,内部[[Prototype]]为null,连__proto__属性都不可访问 - 避免误用
Object.create({}):空对象{}本身仍继承自Object.prototype,不是真正“无原型”
从源头堵住 __proto__ 写入路径
污染发生于用户输入被当作键名或嵌套结构合并进对象时。Object.create(null) 本身不解决输入问题,但提供了安全的落点:
- 所有外部数据(尤其 JSON 解析结果)必须在赋值前清洗:
JSON.parse(str)后立即递归扫描键名,过滤/\b(__proto__|constructor|prototype)\b/i - 深层污染也要防,比如
{"a": {"__proto__": {"x": 1}}}或带点号的键{"x.__proto__.y": "z"},需递归遍历而非只查顶层 - 清洗动作必须放在数据进入业务逻辑的第一步,不能等到合并或赋值环节才处理
慎用高危合并/赋值方法
很多工具函数默认不校验原型键,会把恶意 __proto__ 直接写入目标对象:
- 禁用
_.merge、_.set、_.defaultsDeep处理不可信输入;如必须用,确保 lodash ≥ 4.17.21 -
Object.assign({}, obj)不安全——它会把obj.__proto__当作普通属性复制过去;应改用Object.assign(Object.create(null), obj) - 深拷贝库若未开启原型键校验,同样危险;优先选支持
prototypePollution: false配置的版本
使用时注意无原型对象的边界
Object.create(null) 创建的对象没有 hasOwnProperty、toString 等方法,需调整使用习惯:
- 判断键是否存在:用
Object.prototype.hasOwnProperty.call(dict, key),或封装成const has = Object.prototype.hasOwnProperty.bind(dict) - 遍历用
Object.keys(dict),不用for...in(虽无继承属性,但语义不清) - 序列化可用
JSON.stringify(dict),它只处理自有可枚举属性,不受原型影响 - 避免依赖
instanceof或Object.prototype.toString.call(dict)做类型判断,这类反射行为在 null 原型对象上失效











