子类实例污染父类原型属性源于在prototype上挂载可变对象导致共享引用,正确做法是将可变数据初始化于构造函数内,并对不可信输入过滤敏感键名、使用Object.create(null)及冻结关键原型。

子类实例污染父类原型属性,本质不是继承逻辑出错,而是误把可变对象(如数组、普通对象)直接挂载在父类 prototype 上,导致所有实例共享同一份引用。这不是“死穴”,是可精准规避的设计误区。
别在 prototype 上放可变引用值
常见错误写法:
function Animal() {}
Animal.prototype.skills = ['run', 'jump']; // ❌ 危险:所有 Animal 实例共用这个数组
const dog = new Animal();
const cat = new Animal();
dog.skills.push('bark');
console.log(cat.skills); // ['run', 'jump', 'bark'] —— 被意外修改
正确做法是:把可变数据初始化在构造函数内,确保每个实例独有一份:
- 将
skills移到Animal构造函数中:this.skills = ['run', 'jump']; - 若需默认值复用,可用工厂函数或静态方法生成新副本,例如:
Animal.getDefaultSkills = () => ['run', 'jump'];
警惕深拷贝/合并逻辑侵入原型链
当子类使用 Object.assign、lodash.merge 或自研递归赋值处理用户输入时,若输入含 {"__proto__": {"isAdmin": true}},会直接污染 Object.prototype,进而影响所有对象(包括父类原型)。
- 禁止对不可信输入调用
_.merge({}, userInput)或set(obj, path, value),尤其路径含__proto__、constructor、prototype - 替换默认对象创建方式:用
Object.create(null)代替{}存储动态键值,彻底切断原型链依赖 - 升级关键库:Lodash ≥ 4.17.21,Hoek ≥ 5.0.3;避免
_.set处理外部路径
运行时加固:冻结关键原型(高安全场景)
服务启动初期执行一次即可:
-
Object.freeze(Object.prototype)阻止新增/修改属性(注意:不可逆,确认无合法库依赖扩展) - 对自定义父类原型也做保护:
Object.freeze(Animal.prototype),防止后续误写 - 搭配输入层过滤:递归检查所有键名及嵌套路径,拦截
__proto__等敏感词(正则/\b(__proto__|constructor|prototype)\b/i)
问题根源不在继承机制本身,而在数据初始化位置和外部输入的处理边界。守住这两条线,污染就不会发生。

















