Reputation: 99990
I didn't know about this problem until today, and I want to know what the best way of getting around it is. The problem is that when you instantiate two different objects from the same constructor function, they share the same prototype, which means if one object sets that prototype, all other objects will get altered too.
For example:
function A(obj) {
}
A.prototype = {
events: {
one: 1
}
};
var b = new A();
console.log(b.events);
var c = new A();
console.log(c.events);
b.events.x = 2;
console.log(b.events);
console.log(c.events); //whoops, c also got b's event x
what the heck? What is the best way to get around this?
this is what I came up with, but I am wondering if there is a better way?
var _ = require('underscore');
function A(obj) {
if (obj == null) {
obj = {};
}
if(obj.events == null){
obj.events = {};
}
this.events = _.extend(obj.events, A.prototype.events);
}
A.prototype = {
events: {
one: 1
}
};
var b = new A({events:{three:3}});
console.log(b.events);
var c = new A({events:{four:4}});
console.log(c.events);
b.events.x = 2;
console.log(b.events);
console.log(c.events); //now it's better...(this is crazy)
Upvotes: 0
Views: 51
Reputation: 26696
The first step is to learn prototypical inheritance.
All prototypical inheritance is is a live reference to a value...
And under the covers,
A.prototype.method = function foo () { };
var a = new A();
a.__proto__.method === a.constructor.prototype.method;
a.__proto__ === A.prototype;
unless you're changing the value of `.prototype' to be a new object... ...then your old instances will point to the old prototype object, and your new instances will point to the new prototype object.
This is basic deep-copy versus shallow-copy, value versus reference stuff. There is no Java/C# inheritance with private variables, except through closures (which look nothing like Java/C#).
If you're going to put things on a prototype, they should be only methods, and constants/enums which are statically available to every instance (because of the live reference to .constructor.prototype
's value at the time the instance is made).
And really, unless you're in hardcore memory-pool optimization mode, until ES6 classes are available (and people understand them --- they're just wrappers around .prototype
), this is more mental trouble than it's worth, and is often (though not always) more eloquent to simply build what you need, or use mixins, rather than aiming for JS that looks like Java (but behaves very, very differently).
Upvotes: 1
Reputation: 707228
As you have discovered, the prototype is shared among all instances of a given object. This is done for several reasons including storage efficiency. As such, you should only put things in the prototype that are intended to be identically shared among all instances.
If you want something to be an instance variable that can have a unique value for each separate instance, then it doesn't belong in the prototype. It belongs as an instance variable, set via the this
pointer.
So, if your event
s property is intended to be an instance variable that can be different for every instance, then initialize it in the constructor and it will be unique for each separate instance.
function A(obj) {
this.events = {one: 1};
}
And remove it from the prototype. Just remember that the prototype is designed for things that are identically shared among ALL instances of a given type of object. That's why it's perfect for methods and rarely used for modifiable data.
Working code example:
function A() {
this.events = {one: 1};
}
var x = new A();
var y = new A();
log(x.events);
log(y.events);
log("--------------");
x.events.one = 2;
y.events.one = 3;
log(x.events);
log(y.events);
function log(x) {
var d = document.createElement("div");
if (typeof x === "object") {
x = JSON.stringify(x);
}
d.textContent = x;
document.body.appendChild(d);
}
Upvotes: 1
Reputation: 1080
You shouldn't save variables in your prototype, instead use this in your constructor. In your case the solution would be:
function A(obj) {
this.events = {
one: 1
};
}
var b = new A();
console.log(b.events);
var c = new A();
console.log(c.events);
b.events.x = 2;
console.log(b.events);
console.log(c.events);
Upvotes: 1
Reputation: 386560
why so complicated?
function A(obj) {
this.events = {
one: 1
};
}
Upvotes: 1