【问题标题】:JavaScript prototyping: single prototype object or not?JavaScript 原型设计:单个原型对象与否?
【发布时间】:2010-08-26 03:57:34
【问题描述】:

我并没有真正了解 JavaScript 原型。以这段代码为例:

function Class(asdf) {
 if(typeof(asdf) == 'undefined') {
 } else {
  this.asdf = asdf;
 }
}
Class.prototype.asdf = "default_asdf";
Class.prototype.asdf2 = [];
Class.prototype.change_asdf = function() {
 this.asdf = "changed_asdf";
 this.asdf2.push("changed_asdf2");
}

function SubClass() {
}
SubClass.prototype = new Class("proto_class");
SubClass.prototype.constructor = SubClass;

test1 = new SubClass();
alert("test1 asdf: " + test1.asdf + " " + test1.asdf2);
test1.change_asdf();
alert("test1 asdf: " + test1.asdf + " " + test1.asdf2);
test2 = new SubClass();
alert("test2 asdf: " + test2.asdf + " " + test2.asdf2);

第一个警报按预期打印“proto_class []”。第二个警报也按预期打印“changed_asdf [changed_asdf2]”。但是为什么第三个警报会打印“proto_class [changed_asdf2]”?!如果原始原型对象 (new Class("proto_class")) 正在修改,那么为什么 asdf 变量不保持“changed_asdf”?如果不是,那么为什么 asdf2 数组包含“changed_asdf2”?此外,我如何确保每个新的 SubClass() 实例都包含一个新的 Class() 实例,就像在 C++ 和 Java 中一样?

【问题讨论】:

    标签: javascript oop subclass


    【解决方案1】:

    因为你写

    SubClass.prototype = new Class("proto_class");
    

    您正在创建Class一个实例 的原型。您想要的是创建一个从其父原型继承的子类。正如 David Flanagan 在他的 JavaScript: The Definitive Guide(第 9.5 节)中所展示的,您必须使用辅助函数来创建具有指定原型的新对象:

    function heir(p) {
        function f(){}         // dummy constructor function
        f.prototype = p;       // specify prototype object we want
        return new f();        // create and return new object
    }
    

    (Crockford 将这个函数称为Object.create,在所谓的ES5 object constructor property 之后,但请不要这样做,因为这个can be misleading。)

    在 SubClass 构造函数中,您必须将 this 设置为当前对象的 call Class 构造函数:

    function SubClass() {
        // call the parent's constructor with
        // `this` set to the current scope
        Class.call(this, "proto_class");
    }
    

    最后但并非最不重要的一点是,您只重置了一次Class.asdf2,而不是在ClassSubClass 的构造函数中。所以将this.asdf2 = []; 添加到其中一个构造函数中。

    现在完整的代码如下:

    function heir(p) {
        function f(){}         // dummy constructor function
        f.prototype = p;       // specify prototype object we want
        return new f();        // create and return new object
    }
    
    function Class(asdf) {
        if (typeof asdf != 'undefined')
            this.asdf = asdf;
    }
    
    Class.prototype.asdf = "default_asdf";
    Class.prototype.asdf2 = [];
    Class.prototype.change_asdf = function() {
        this.asdf = "changed_asdf";
        this.asdf2.push("changed_asdf2");
    }
    
    function SubClass() {
        // call the parent's constructor with
        // `this` set to the current scope
        Class.call(this, "proto_class");
        this.asdf2 = [];
    }
    
    SubClass.prototype = heir(Class.prototype);
    SubClass.prototype.constructor = SubClass;
    
    test1 = new SubClass();
    alert("test1 asdf: " + test1.asdf + " " + test1.asdf2);
    test1.change_asdf();
    alert("test1 asdf: " + test1.asdf + " " + test1.asdf2);
    test2 = new SubClass();
    alert("test2 asdf: " + test2.asdf + " " + test2.asdf2);
    

    【讨论】:

      【解决方案2】:

      这是因为asdf2Class.prototype 上的可变数组。该数组由委托给该原型的所有实例共享。如果您希望每个实例都有一个单独的asdf2,则必须通过某种方法将其分配给this.asdf2

      请注意,您分配了this.asdf,但您从不分配this.asdf2,您只需推送到现有数组。

      var house = {nice: true};
      var me = {home: house};
      var roomie = {home: house};
      
      // Now roomie has a party and trashes the place.
      roomie.home.nice = false;
      
      //and how's my house?
      me.home.nice === false;
      // because house is shared.
      

      本例中house的分享与问题中asdf2的分享相同。

      【讨论】: