JavaScript原型对象(prototype)使用的常见陷阱与最佳实践

作者:小括号时间:2026-10-22 07:43:30浏览:552

本文深入解析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

尽管可以扩展内置对象,但不建议随意这样做。在多人协作的开发环境中,这极易导致方法被意外覆盖或造成全局污染。

以上内容总结了使用原型对象时的关键注意事项,希望能为读者提供有益参考。

作者最新文章