3

In my JavaScript project I define an object, then create multiple instances using Object.create(). The object has several (string and int) properties, each of which is unique per instance. However, if I use an array property, all instances share the same array.

This code easily demonstrates this:

TestThing = {
    code: "?",
    intlist: [],

    addint(i) {
        alert("Adding " + i + " to " + this.code + ", list had " + this.intlist.length + " ints");
        this.intlist.push(i);
    }
}

var thing1 = Object.create(TestThing);
thing1.code = "Thing 1";
var thing2 = Object.create(TestThing);
thing2.code = "Thing 2";

thing1.addint(11);
thing2.addint(42);

alert(thing2.intlist);  // will output 11,42

So, what causes this? How do I solve this issue?

3
  • 1
    "The object has several (string and int) properties, each of which is unique per instance." - no, it's just that you treat them in a different way. thing1.code = "Thing 1"; is fundamentally different to thing1.intlist.push(11);. One's modifying, one's replacing. Commented Nov 2, 2017 at 18:11
  • 1
    The inner array is not a copy..it is a reference to original array Commented Nov 2, 2017 at 18:12
  • So how do I make sure each instance has it's own array? edit: I mean I know how to replace the array with a new ne per instance, but that's not very elegant. What would be best practice in this case? Commented Nov 2, 2017 at 18:15

2 Answers 2

1

With reference-type properties, each child gets a reference to the same object. Any change any child makes to the object are visible to all instances.

You need to either implement a constructor to set up the property, or have the code that uses the property set it up the first time through. (If you want to use a constructor and Object.create, though, you'll have to call it yourself; Object.create won't call it for you.)

You could do something like this...

TestThing = {
    code: "?",
    intlist: null,
    addint : (i) => {
        if (!this.intlist) this.intlist = [];
        alert("Adding " + i + " to " + this.code + ", list had " + this.intlist.length + " ints");
        this.intlist.push(i);
    }
}

Or, less error-prone-ly (albeit forsaking Object.create)...

class TestThing {
    constructor(code) {
        this.code = code;
        this.intlist = [];
    }

    addint(i) {
        alert("Adding " + i + " to " + this.code + ", list had " + this.intlist.length + " ints");
        this.intlist.push(i);
    }
}

var thing1 = new TestThing("Thing 1");
var thing2 = new TestThing("Thing 2");

thing1.addint(11);
thing2.addint(42);

alert(thing2.intlist);  // will output 42

Unfortunately, if you're coding for web browsers, IE (even IE 11) doesn't seem to support class. So you'll have to stick with the old way of defining classes.

TestThing = function(code) {
    this.code = code;
    this.intlist = [];
};

TestThing.prototype = {
    addint: function(i) {
        alert("Adding " + i + " to " + this.code + ", list had " + this.intlist.length + " ints");
        this.intlist.push(i);
    }
};
Sign up to request clarification or add additional context in comments.

6 Comments

I can see how this works around the issue, but this solution becomes almost unworkable if I have a lot of functions (or even other objects) accessing the property. I was hoping for a way to create the inheriting objects without running into this problem.
That'd be a job for a constructor. Unfortunately, if you use Object.create, it won't call the constructor for you.
@cHao however the second parameter may help: const instance = Object.create(parent, {list: [] });
Thanks @cHao, I'll think I'll switch to the more elegant way in your second example.
@Jonasw: That second argument to Object.create isn't quite as cool as it first looks. (Its values are supposed to be property descriptors.) Plus, it's still error-prone, though. You could, if you wanted, say Object.new = function(parent, ...args) { let retval = Object.create(parent); retval.constructor(...args); return retval; }, if you wanted.
|
-2

To fix this you use concat instead of push, like so:

this.intlist = this.intlist.concat(i);

Why this happens ? Because push mutates the array, concat doesn't, and an array in javascript is also an object, hence, the memory reference to that array is the same.

3 Comments

I feel this is a dangerous way to do this - all instances will still share the same array until they add something to it. I want to use the array in several ways, not just this one function. Your solution seems error-prone, and inefficient if I use the add function a lot (say, 10,000's of times).
you can also use Object.assign({}, TestThing) or use spread operator to add to the list.
But the problem is not adding things to the list (the code is just an example). The problem is that I may have several functions and external objects and functions accessing the property, and using it in several ways. For this, all instances should have their own instance of the array.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.