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
?
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/
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
.
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>");
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With