Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

OOP Javascript - Isolate object within class

I'm attempting to have a main object that I can create multiple instances of, that each inherit the children (with unique/isolated properties). When I do this, however, the properties of the object (after being changed) are changing for all created objects. I may not be explaining this correctly, but the example should be pretty clear.

Main = function(){};

// Extending the main class with new object. Doing it this way so I can have these in
// separate files.
Main.prototype.foo = {
    bar: 1
}

// First instance of Main().
var A = new Main();

// Second instance of Main().
var B = new Main();

// Set the bar property to different values for each Main() object.
A.foo.bar = 2;
B.foo.bar = 3;

// Both A.foo.bar and B.foo.bar return 3.
alert(A.foo.bar);
alert(B.foo.bar);

What I'm trying to get to have happen, is for A.foo.bar to return 2 and B.foo.bar to return 3, so that I have isolated objects that are independent of each other.

Any ideas? Am I just missing something that's obvious? Would be much appreciated!

like image 299
Erik Smith Avatar asked Jun 13 '11 01:06

Erik Smith


3 Answers

The "foo" property is on the prototype object, and there's only one of those. When you set it via any instance, you're affecting that same shared property.

You can add an instance property in your constructor:

  function Main() {
    this.instanceProperty = 1;
  }

Then that'll be per-instance.

The prototype is not a "master template" or anything like that; it's a real object. It's not copied onto instances. Instead, the runtime knows it's there, and when references are made to properties on an instance that don't actually exist on the instance, then it knows to walk up the prototype chain and look for properties there.

like image 83
Pointy Avatar answered Nov 10 '22 00:11

Pointy


The other answers are more or less correct, but what they're missing is that there's a difference between

Main.prototype.foo = {
    bar: 1
};

and

Main.prototype.bar = 1;

In both cases, instantiating a new Main will create a new instance with, in its prototype chain, a property foo or bar. In both cases, the instance-level property can be redefined without affecting other instances:

function Main() {};
Main.prototype.foo = {
    bar: 1
};
Main.prototype.bar = 1;

a = new Main();
b = new Main();

a.foo = { bar: 2 };
console.log(a.foo.bar, b.foo.bar); // 2 1

a.bar = 2;
console.log(a.bar, b.bar); // 2 1

But when you instantiate a new Main, the instance variable foo is a reference to a single object, {bar:1}, which is shared among all instances. So when you set a.foo.bar, you're changing the common object, not an instance variable; the instance variable is the reference a.foo.

You don't have to initialize the instance property in the constructor. The standard approach would be to set bar directly on the prototype, i.e. Main.prototype.bar = 1, which would give you independent instance variables initialized to 1. However, if you need a more complex data structure (an object, an array, or an instance of another class) on a per-instance basis, then you can't create this as a property on the prototype, because you'll be giving every instance a reference to a common object - so inside the constructor is the way to go:

function Main() {
    // instance-level object
    this.foo = {
        bar: 1
    };
}
like image 31
nrabinowitz Avatar answered Nov 10 '22 00:11

nrabinowitz


Since you're editing something on the prototype, it's going to affect every object.

However, you can do:

A.x = 2;
B.x = 3;

Then you'll have different results.

Or, you can have something like this:

Main = function(val){
  this.x = val;
}

A = new Main(2);
B = new Main(3);
like image 40
Newtang Avatar answered Nov 10 '22 00:11

Newtang