JavaScript 原生对象扩展的正确姿势:避免枚举污染
本文介绍如何安全地扩展 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
});
} JavaScript怎么遍历对象的Symbol键和不可枚举属性
2026-11-05 13:13:39
封装一个健壮的JS字符串判空工具函数
2026-11-05 12:16:14
JavaScript合并数组的三种常用方法:concat、扩展运算符与push
2026-11-05 11:22:29
JavaScript立即执行函数(IIFE)是什么及基本语法
2026-11-05 10:51:19
JS对象数组转字符串为什么推荐用JSON.stringify()
2026-11-05 09:40:31
