如何在JavaScript解构参数对象中安全检查属性?解决Object.prototype污染问题的编码模式
在JavaScript开发里,检查函数参数是否存在是家常便饭。比如普通参数列表f(x,y)里,我们可以用typeof x这类方式轻松检查,但如果函数是用对象解构f({x,y})来声明参数的,那怎么准确判断属性是否存在呢?
这里有个容易踩的坑:要是Object.prototype被新增了未知属性(比如第三方库不小心修改了原型),常规的检查方法很容易误判,进而导致应用故障。之前那种在函数体内用Object.hasOwn逐个检查属性的方案,写起来又啰嗦又冗余,参数多了简直没法维护。
严格来说,核心问题就是:当Object.prototype存在未知属性时,采用何种编码模式可以减少JavaScript对象字面量参数列表检查的冗余?
可行解决方案:隔离原型的解构模式
经过社区思路贡献,我们找到了一个简洁的编码模式,能避免对长参数列表逐个做污染检查,而且参数名称完全不受Object.prototype属性的影响。这个方案把检查负担从调用端转移到了函数定义端,示例代码如下:
f({ x: 1 }); function f(arg) { let { toString, x, y, z } = { ...arg, __proto__: null }; console.log(toString || x); // 输出1,因为toString未传入,不会拿到原型上的方法 };
方案原理
这里的关键是{ ...arg, __proto__: null }:我们把传入的arg对象展开,同时显式将新对象的原型设为null,让它彻底脱离Object.prototype的继承链。这样后续解构时,像toString这类原本在原型上的属性,就不会被误当成是调用者传入的参数——只有当调用者真的传了该属性时,我们才能拿到对应的值,完美规避了原型污染带来的误判。
常见失败方案分析
为了让大家更清楚为什么上述方案有效,咱们来看看几种常见的失败尝试:
1. 直接解构参数
// 调用示例:f({ obj1:{} }); function f({ toString, obj1 }){ var existing = toString || obj1; // 误判:toString会拿到Object.prototype.toString,而非undefined };
问题:直接解构时,会自动继承Object.prototype的属性,就算调用者没传toString,也会拿到原型上的方法,导致误判参数存在。
2. 展开对象后解构(未隔离原型)
function f(arg){ let { toString, x } = {...arg}; var existing = toString || obj1; // 同样失败:展开后的对象原型还是Object.prototype };
问题:展开arg生成的新对象依然继承Object.prototype,原型上的属性还是会被解构出来,无法避免误判。
3. 给解构属性设默认值(对象展开版)
function f(arg){ let { toString=null, obj1=null }={...arg}; console.log( toString || obj1 ); // 失败:toString拿到原型方法,不会触发默认值null };
问题:默认值只有当属性不存在时才会生效,但原型上的toString是存在的,所以会直接拿到原型方法,默认值完全不起作用。
4. 直接在解构参数里设默认值
function f({ toString=null, obj1=null }){ console.log( toString || obj1 ); // 失败:同上,原型属性覆盖默认值判断 };
问题:和前一种情况一样,原型上的属性会被优先解构,默认值无法触发,导致误判。
内容的提问来源于stack exchange,提问作者Bitlab

