Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Javascript prototypes and instance creation

I apologize because this topic comes up a lot, but I have not been able to have this adequately explained in anything I've read today.

I am trying to make a simple collection class (and learn about javascript prototyping at the same time) designed to store objects with a "name" property and lets its members be accessed by index or value. So far I've got this:

function Collection() {}
Collection.prototype.constructor = Collection;
Collection.prototype._innerList = [];
Collection.prototype._xref = {};
Collection.prototype.count = function () { return this._innerList.length; };
Collection.prototype.add = function (obj) {
    this._xref[obj.name] = this._innerList.push(obj) - 1;
}
Collection.prototype.get = function (id) {
    if (typeof id == "string") {
        return this._innerList[this._xref[id]];
    } else {
        return this._innerList[id];
    }
};

http://jsfiddle.net/YrVFZ/

The problem:

var foo = new Collection();
foo.add({name: "someitem", value:"hello world"});   // foo.count()== 1

var bar= new Collection();
bar.add({name: "someotheritem", value:"hello world"}); // bar.count()== 2

Hmm...

Basically, the new instance bar is created with all the properties having the current values of the data in foo. I know I can fix this by putting _xref, etc. inside the constructor, but I'm trying to understand how prototyping works. If I create a new instance, and make changes to the data in that instance, why would those values carry over when I create another new instance?

If I make further changes to a property from the prototype of foo or bar they are independent, so it doesn't seem as if I'm somehow referencing the same instance of anything. So what is causing bar to be instantiated with the current values from foo?

like image 360
Jamie Treworgy Avatar asked Apr 29 '11 21:04

Jamie Treworgy


2 Answers

Consider a classroom full of students. Putting something on the prototype is like putting something on the white board for them all to see. When you're declaring

Collection.prototype._innerList = [];

you're giving every collection that property; regardless of calling new Collection() any changes to the white board affects all students. However, if you define it within the constructor, or one of the functions as this.variableName = [], each copy will have its own variableName, like handing each student a handout. Obviously, there's some cases when it's okay to have something on the white board, such as instructions that will be universal from student to student, but if each item is going to be different for each student, it should be an individual property. Hope this explanation makes sense...

You want to be doing this.

function Collection() {
    if (!this instanceof Collection)
        return new Collection();
    this._innerList = [];
    this._xref = {};

    return this;
}

Collection.prototype.count = function() {
    return this._innerList.length;
};
Collection.prototype.add = function(obj) {
    this._xref[obj.name] = this._innerList.push(obj) - 1;
}
Collection.prototype.get = function(id) {
    if (typeof id == "string") {
        return this._innerList[this._xref[id]];
    } else {
        return this._innerList[id];
    }
};

var foo = new Collection();
foo.add({name: "someitem", value:"hello world"});   
console.log(foo.count()); // 1

var bar= new Collection();
bar.add({name: "someotheritem", value:"hello world"});
console.log(bar.count()); // 1

http://jsfiddle.net/vXbLL/

Edit

Not really relevant to your question, but it's something I do so I will throw it out there. Whenever I'm doing something on the prototype, if I'm not returning something, I return this. It allows chaining, so you could do instance.function1().function2().function3() as long as function1 and function2 return this.

like image 59
Robert Avatar answered Sep 21 '22 17:09

Robert


You can think of a prototype as giving all objects of that class shared variables. Like static variables in a c++ class if that makes any sense. That's ok to do for functions because they're all the same for each instance of the class. However, if you want the object to have its own non-shared variable, you shouldn't use the prototype. One simple way to do it is to assign them in the constructor method like this:

function Collection() 
{
this._innerList = [];
this._xref = {};
}

Collection.prototype.count = function () { return this._innerList.length; };
Collection.prototype.add = function (obj) {
    this._xref[obj.name] = this._innerList.push(obj) - 1;
}
Collection.prototype.get = function (id) {
    if (typeof id == "string") {
        return this._innerList[this._xref[id]];
    } else {
        return this._innerList[id];
    }
};

var foo = new Collection();
foo.add({name: "someitem", value:"hello world"});   // foo.count()== 1
document.write(foo.count(),"<br>");

var bar= new Collection();
bar.add({name: "someotheritem", value:"hello world"}); // bar.cou
document.write(bar.count(),"<br>");
like image 22
Brian Rothstein Avatar answered Sep 24 '22 17:09

Brian Rothstein