JavaScript原型对象(prototype)使用的常见陷阱与最佳实践
本文深入解析JavaScript中原型对象(prototype)的使用陷阱。重点讨论重写prototype导致constructor指向Object的问题,以及先实例化后重写原型造成的方法调用失败。同时警示共享引用类型可能引发的数据污染,并指出随意扩展内置对象在团队协作中的覆盖风险。
原型对象重写与constructor丢失问题
首先展示一个结合构造函数与原型对象的简单示例。
function CreateObj( uName, uAge ) {
this.userName = uName;
this.userAge = uAge;
}
CreateObj.prototype.showUserName = function () {
return this.userName;
}
CreateObj.prototype.showUserAge = function () {
return this.userAge;
}
该示例虽然功能正常,但代码结构较为冗余。每添加一个新方法都需要单独操作一次原型对象。为了优化这一点,可以将原型对象视为一个字面量对象,将所有方法统一在该字面量中定义,从而实现相同的功能效果。
CreateObj.prototype = {
showUserAge : function(){
return this.userAge;
},
showUserName : function(){
return this.userName;
},
}
var obj1 = new CreateObj( 'ghostwu', 22 );
var obj2 = new CreateObj( '卫庄', 24 );
console.log( obj1.showUserName(), obj1.showUserAge() ); //ghostwu 22
console.log( obj2.showUserName(), obj2.showUserAge() ); //卫庄 24
然而,这种直接赋值字面量对象来重写原型的方式会引发一个问题:构造函数属性constructor不再指向原本的构造函数。
在未进行重写操作之前,constructor属性正确指向CreateObj构造函数。
function CreateObj( uName, uAge ) {
this.userName = uName;
this.userAge = uAge;
}
CreateObj.prototype.showUserName = function () {
return this.userName;
}
CreateObj.prototype.showUserAge = function () {
return this.userAge;
}
console.log( CreateObj.prototype.constructor === CreateObj ); //true
一旦执行重写操作,constructor属性将指向Object构造函数。
CreateObj.prototype = {
showUserAge : function(){
return this.userAge;
},
showUserName : function(){
return this.userName;
},
}
console.log( CreateObj.prototype.constructor === CreateObj ); //false
console.log( CreateObj.prototype.constructor === Object ); //true
由此可见,由于原型被重写,constructor属性无法再准确标识对象的原始构造函数。
实例化时机与原型重写的影响
此前编写的代码通常遵循“先扩展原型方法,后实例化对象”的模式。现在考察一种相反的情况:先创建实例对象,随后再向原型对象添加函数。
此时,实例对象是否还能正常调用到后续扩展的方法?
function CreateObj( uName, uAge ) {
this.userName = uName;
this.userAge = uAge;
}
var obj1 = new CreateObj( 'ghostwu', 22 );
CreateObj.prototype.showUserName = function(){
return this.userName;
}
console.log( obj1.showUserName() ); //ghostwu
如果原型未被重写,实例可以正常调用扩展方法;但如果原型对象已被重写,则无法调用。
function CreateObj( uName, uAge ) {
this.userName = uName;
this.userAge = uAge;
}
var obj1 = new CreateObj( 'ghostwu', 22 );
CreateObj.prototype = {
showUserName : function(){
return this.userName;
}
}
console.log( obj1.showUserName() ); //报错
原型共享引用类型的风险
原因在于,若先实例化再重写原型,实例的隐式原型__proto__仍指向旧的原型对象,而非新重写的原型对象,导致方法调用失败。此外,需特别注意原型中引用类型的共享特性:多个实例共享同一原型,若其中一个实例修改了引用类型的值,其他实例也会受到影响。
function CreateObj(){}
CreateObj.prototype = {
name : 'ghostwu',
skills : [ 'php', 'javascript', 'linux' ]
};
var obj1 = new CreateObj();
obj1.skills.push( 'python' );
var obj2 = new CreateObj();
console.log( obj2.skills ); //'php', 'javascript', 'linux', 'python'
扩展内置对象的最佳实践
利用原型对象的共享特性,可以方便地为内置对象扩展方法,例如实现数组去重功能。
Array.prototype.unique = function(){
var res = [];
for( var i = 0, len = this.length; i < len; i++ ){
if( res.indexOf( this[i] ) == -1 ) {
res.push( this[i] );
}
}
return res;
}
var arr = [ 10, 20, 30, 20, 30, 20, 40, 20 ];
console.log( arr.unique() ); //10, 20, 30, 40
尽管可以扩展内置对象,但不建议随意这样做。在多人协作的开发环境中,这极易导致方法被意外覆盖或造成全局污染。
以上内容总结了使用原型对象时的关键注意事项,希望能为读者提供有益参考。
JS语法错误会阻止加载吗?区分SyntaxError与资源加载失败
2026-11-05 16:08:46
JavaScript常见SyntaxError语法错误排查与修复方法
2026-11-05 13:58:44
JS中slice、substring和substr截取字符串的区别
2026-11-05 13:43:47
JS 转 TS 手动迁移最佳实践:从配置 tsconfig 到消除 any 类型
2026-11-05 11:53:32
为什么JS数组push后得到的是数字而不是数组?
2026-11-05 09:59:18
