views:

63

answers:

2

I don't really get JavaScript prototyping. Take this code, for instance:

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);

The first alert prints "proto_class []", as expected. The second alert prints "changed_asdf [changed_asdf2]", also as expected. But why does the third alert print "proto_class [changed_asdf2]"?! If the original prototype object (new Class("proto_class")) is being modified, then why doesn't the asdf variable remain "changed_asdf"? And if it isn't, then why does the asdf2 array contain "changed_asdf2"? Furthermore, how do I make sure that each new SubClass() instance contains a fresh instance of Class(), as in C++ and Java?

+1  A: 

It's because you write

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

you are creating a prototype of one instance of Class. What you want is to create a sub-class that inherits from its parent's prototype. As David Flanagan shows in his JavaScript: The Definitive Guide (§ 9.5), you have to use a helper function to create a new object with a specified prototype:

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 calls this function Object.create, after the so-called ES5 object constructor property, but please don't do this, as this can be misleading.)

Within the SubClass constructor, you have to call the Class constructor with this set to the current object:

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

And last, but not least, you only reset Class.asdf2 once, but not in the constructor function of Class or SubClass. So add this.asdf2 = []; to one of the constructors.

The complete code now reads:

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);
Marcel Korpel
A: 

This is because asdf2 is a mutable array on Class.prototype. That array is shared by all instances that delegate to that prototype. If you want each instance to have a separate asdf2, you have to assign it to this.asdf2 in some method.

Note that you assign this.asdf, but you never assign this.asdf2, you just push on to the existing array.

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.

The sharing of house in this example is the same as the sharing of asdf2 in the question.

Sean McMillan