前面文章中讨论了JS开发中对象属性枚举的ES3和ES5方案并给出了一组常用工具函数,其实,企业开发中真正应用时还存在不少问题。本文想基于前文进一步探讨一下有关原型污染的问题。由于JS的先天不足,有关原型污染背后隐藏着一个大的“故事”,以后我们的文章中还要涉及其中一些情节。
问题
前面在讨论使用in运算符检测对象中是否存在属性的方案,但是通过所举的示例也发现一个问题,例如:
console.log(‘"ID" in contacts: ‘,"ID" in contacts);
其输出结果也是true。这说明in运算符在属性检测时不仅搜索当前对象的自有属性,还会沿着对象的原型链搜索。
根据前面对于属性继承的分析可得知,JS中的对象总是以继承的方式工作,即使是一个空的对象字面量也会继承Object.prototype的大量属性。因此,对于下面的测试结果正在我们的意料之中:
var pol={};
console.log("Hi" in pol); //false
console.log("toString" in pol); //true
console.log("valueOf" in pol); //true
console.log("constructor" in pol); //true
console.log("__defineGetter__" in pol); //true
console.log("__defineSetter__" in pol); //true
console.log("__lookupGetter__" in pol); //true
console.log("__lookupSetter__" in pol); //true
console.log("hasOwnProperty" in pol); //true
console.log("isPrototypeOf" in pol); //true
console.log("propertyIsEnumerable" in pol); //true
console.log("toLocaleString" in pol); //true
而在ES5中使用Object.prototype中的hasOwnProperty方法正好可以避免上面的问题,因为它只检索对象的自有属性,包括不可枚举的属性(ES3中没有定义这样的概念)。
更进一步
如果对象本身有一个自有属性hasOwnProperty,情况又该如何呢?参考如下代码:
var o={};
o.hasOwnProperty="*********";
console.log(o.hasOwnProperty("Alice");
运行测试时,出现如下图所示的运行时错误:
对于这种情况,专家的建议是“最安全的方法是不做任何假设”。于是,我们可以提前在任何安全的位置提取出hasOwnProperty方法,同时利用立即执行的匿名函数的词法作用域特点,实现如下解决方案:
(function testOwnProperty(){
//var hasOwn=Object.prototype.hasOwnProperty;也可以使用如下更简洁方式
var hasOwn={}.hasOwnProperty;
var dict={};
dict.Alice=12;
console.log("------------");
console.log(hasOwn.call(dict,"hasOwnProperty"));
console.log(hasOwn.call(dict,"Alice"));
dict.hasOwnProperty=100;
console.log("-------------");
console.log(hasOwn.call(dict,"hasOwnProperty"));
console.log(hasOwn.call(dict,"Alice"));
console.log("---------------");
})();
于是,不管对象的hasOwnProperty方法是否被覆盖,上述方案都能够正常工作。值得注意的是,许多知名JS库就是利用了上述技术。