JavaScript 原生对象扩展的正确姿势:避免枚举污染

作者:异步漫步

时间:2026-10-26

浏览:345

本文介绍如何安全地扩展 JavaScript 原生对象。直接赋值会因 enumerable 默认为 true 导致 for-in 循环污染,推荐使用 Object.defineProperty 将 enumerable 设为 false。文中提供了 capitalize、pxToInt、htmlEntities 等实用字符串扩展方法,并指出直接修改原型可能引发全局污染,建议谨慎使用。

文章目录

在《JavaScript 每周导读》第 3 期中,作者推荐了一篇关于如何正确扩展 JavaScript 的文章。这里并非逐字翻译,而是力求通俗易懂地分享核心观点。

直接扩展原型链的缺陷

原文标题为 Extending JavaScript – The Right Way。JavaScript 内置了许多强大方法,但有时我们需要特定功能。如何优雅地扩展 JavaScript 功能?例如,我们希望增加一个 capitalize() 方法来实现字符串首字母大写。

通常的实现方式如下:

 
if(!String.prototype.capitalize)
{ 
String.prototype.capitalize = function()
{ 
return this.slice(0,1).toUpperCase() + this.slice(1).toLowerCase();
} 
} 

上述代码看似正常,但如果执行以下遍历操作:


代码如下:

 
var strings = "yay";
for(i in strings) console.log(i + ":" + strings[i]);

我们会发现输出结果包含了我们添加的方法:


0: y


1: a


2: y


capitalize: function () { return this.slice(0, 1).toUpperCase() + this.slice(1).toLowerCase(); }


这是因为通过直接赋值添加的方法,其 enumerable 属性默认为 true,导致在 for-in 循环中被遍历出来,这通常不是我们想要的。


使用 Object.defineProperty 安全扩展

为了避免这个问题,我们可以使用 Object.defineProperty 方法,并将 enumerable 属性显式设置为 false。


代码如下:

 
if(!String.prototype.capitalize)
{ 
Object.defineProperty(String.prototype, 'capitalize',
{ 
value: function()
{ 
return this.slice(0,1).toUpperCase() + this.slice(1).toLowerCase();
}, 
enumerable: false
}); 
} 

再次运行之前的遍历代码:


代码如下:

 
var strings = "yay";
for(i in strings) console.log(i + ":" + strings[i]);

此时输出结果恢复正常,不再包含自定义方法:


0: y


1: a


2: y


需要注意的是,for-in 循环不输出并不代表方法不存在。我们可以通过直接访问来验证:


代码如下:

 
var strings = "yay";
console.log(strings.capitalize)

控制台会输出:


代码如下:

 
function () { return this.slice(0, 1).toUpperCase() + this.slice(1).toLowerCase(); }

这种扩展方式非常灵活,允许我们定义自己的对象并设置默认值。


常用字符串扩展方法示例

以下是几个可以在项目中使用的扩展方法示例:


String.pxToInt()


将 "200px" 这样的字符串转换为数字 200:


代码如下:

 
if(!String.prototype.pxToInt)
{ 
Object.defineProperty(String.prototype, 'pxToInt',
{ 
value: function()
{ 
return parseInt(this.split('px')[0]);
}, 
enumerable: false
}); 
} 

String.isHex()


判断字符串是否为 16 进制表示,如 "#CCC" 或 "#CACACA":


代码如下:

 
if(!String.prototype.isHex)
{ 
Object.defineProperty(String.prototype, 'isHex',
{ 
value: function()
{ 
return this.substring(0,1) == '#' &&
(this.length == 4 || this.length == 7) &&
/^[0-9a-fA-F]+$/.test(this.slice(1));
}, 
enumerable: false
}); 
} 

String.reverse()


字符串反转:


代码如下:

 
if(!String.prototype.reverse)
{ 
Object.defineProperty(String.prototype, 'reverse',
{ 
value: function()
{ 
return this.split( '' ).reverse().join( '' );
}, 
enumerable: false
}); 
} 

String.wordCount()


统计单词数量,以空格分隔:


代码如下:

 
if(!String.prototype.wordCount)
{ 
Object.defineProperty(String.prototype, 'wordCount',
{ 
value: function()
{ 
return this.split(' ').length;
}, 
enumerable: false
}); 
} 

String.htmlEntities()


将 HTML 标签如 < 和 > 编码为特殊字符:


代码如下:

 
if(!String.prototype.htmlEntities)
{ 
Object.defineProperty(String.prototype, 'htmlEntities',
{ 
value: function()
{ 
return String(this).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"');
}, 
enumerable: false
}); 
} 

String.stripTags()


去除 HTML 标签:


代码如下:

 
if(!String.prototype.stripTags)
{ 
Object.defineProperty(String.prototype, 'stripTags',
{ 
value: function()
{ 
return this.replace(/<\/?[^>]+>/gi, '');
}, 
enumerable: false
}); 
} 

String.trim()


去除首尾空格:


代码如下:

 
if(!String.prototype.trim)
{ 
Object.defineProperty(String.prototype, 'trim',
{ 
value: function()
{ 
return this.replace(/^\s*/, "").replace(/\s*$/, "");
}, 
enumerable: false
}); 
} 

String.stripNonAlpha()


去除非字母字符:


代码如下:

 
if(!String.prototype.stripNonAlpha)
{ 
Object.defineProperty(String.prototype, 'stripNonAlpha',
{ 
value: function()
{ 
return this.replace(/[^A-Za-z ]+/g, "");
}, 
enumerable: false
}); 
} 

Object.sizeof()


统计对象的大小,例如 {one: “and”, two: “and”} 返回 2:


代码如下:

 
if(!Object.prototype.sizeof)
{ 
Object.defineProperty(Object.prototype, 'sizeof',
{ 
value: function()
{ 
var counter = 0;
for(index in this) counter++;
return counter;
}, 
enumerable: false
}); 
} 


这种扩展 JS 原生对象功能的方式虽然不错,但除非项目中有大量使用,否则不建议直接扩展原生对象,因为这会造成全局变量污染。


另外,文中的 pxToInt() 方法并非必要,JS 内置的 parseInt() 即可实现:parseInt("200px") 同样返回 200。


原文中的 htmlEntities 方法可能存在兼容性问题,这里提供一个更可靠的替代方案:


代码如下:

 
if(!String.prototype.htmlEntities)
{ 
Object.defineProperty(String.prototype, 'htmlEntities',
{ 
value: function()
{ 
var div = document.createElement("div");
if(div.textContent){
div.textContent=this;
} 
else{
div.innerText=this;
} 
return div.innerHTML;
}, 
enumerable: false
}); 
} 

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章