1

I have a Vue.js component with simple template

<div @click="createTargets(2)">
text
</div>

and script file is

export default {
  name: 'test',
  data() {
    return {
      targets: [],
    };
  },
  methods: {
    createTargets(targetCount) {
      this.targets = [];
      var emptyTarget = {
          id: null,
      };
      for (var i = 0; i < targetCount; i++) {
        var targetToPush = emptyTarget;
        targetToPush.id = i;
        console.log(targetToPush.id);
        this.targets.push(targetToPush);
        console.log(this.targets);
      }
      return {};
    },
  },
}

When I click text, I get output

0
[{"id":1},{"id":1}]
1
[{"id":1},{"id":1}]

I cannot figure out why this is happening.

I would expect

0
[{"id":0}]
1
[{"id":0},{"id":1}]

Any ideas?

4
  • 1
    You have a single emptyTarget object that you keep updating and pushing. A reference to an object is not a copy of an object. Commented Mar 15, 2021 at 16:29
  • Oh, so instead of var targetToPush = emptyTarget I should use = {...emptyTarget}. Thank you. But still, the second console log gives me [{"id":0},{"id":1}] just like the fourth one, when I would expect just [{"id":0}]. Why? Commented Mar 15, 2021 at 16:34
  • 1
    You could check if it's a race condition of the browser with; `setTimeout(() => {console.log(this.targets);}, 10); Commented Mar 15, 2021 at 17:26
  • 1
    console.logs will update themselves over time; it's not always useful for tracking processes over time if it's logging object refs. Commented Mar 15, 2021 at 17:33

4 Answers 4

2

The answer is quite simple really, an object is initialized only once, when it is assigned to a variable. If you assign this variable to a new variable, you are assigning the same object reference to a new variable. Updating Object1 will update Object2 and vice versa.

To circumvent this behavior, you can create a copy of the object when initializing Object2 using the new spread operator:

const targets = [];
const common = { commonProp: 'test' };

for (let i = 1; i <= count; i++) {
  const target = { ...common, id: i };
  targets.push(target);
}

this.targets = targets;

Note that you should avoid mutating your component's state in a loop. Even though the render loop is optimized and won't actually render count times, it's still better to mutate your property only once as per example.

Also note that nested objects behave the same way. The solution above is called a shallow copy, in contrast, a deep copy will recursively crawl your object to copy sub objects/arrays.

const common = {
  commonProp: { a: 1, b: 2 }
};
const object1 = { ...common, id: 1 };
const object2 = { ...common, id: 2 };
object1.commonProp.a = 2;
console.log(object1); // { commonProp: { a: 2, b: 2 } }
console.log(object2); // { commonProp: { a: 2, b: 2 } }

To avoid this issue, you can use a library to deep copy an object/array or create a class or factory function that will return a new object every time it is called.

// factory
const createTarget = id => ({
  commonProp: { a: 1, b: 2 },
  id,
});

// class
class Target {
  constructor(id) {
    this.id = id;
    this.commonProp = { a: 1, b: 2 };
  }
}

for (let i = 1; i <= count; i++) {
  const target = createTarget(i); // or new Target(i);
  targets.push(target);
}

I hope this explanation helped you understand this concept a bit better.

Good luck ;)

Sign up to request clarification or add additional context in comments.

Comments

2

console.log()

Please be warned that if you log objects in the latest versions of Chrome and Firefox what you get logged on the console is a reference to the object, which is not necessarily the 'value' of the object at the moment in time you call console.log(), but it is the value of the object at the moment you open the console

Don't use console.log(obj), use console.log(JSON.parse(JSON.stringify(obj)))

Comments

0

You might print the array values (not the reference):

for (var i = 0; i < targetCount; i++) {
  var targetToPush = emptyTarget;
  targetToPush.id = i;
  console.log(targetToPush.id);
  this.targets.push(targetToPush);

  // like this (es6 way):
  console.log([...this.targets]);

  // or like this (old way):
  console.log(this.targets.slice());
}

When you console.log(this.targets) you are printing directly the variable reference, and it's values will be chaging in the next loop, so it's also going to be changed in the browser console.

When you console.log([...this.targets]) you are printing the array values (not the reference) of that given loop interaction and they will stay the same, even if the array changes later.

Comments

0

the main problem , you are updating the reference variable, so it i will keep always the latest data. just do like below hope problem will solve

createTargets(targetCount) {
      this.targets = [];
      for (var i = 0; i < targetCount; i++) {
        var targetToPush = {id: null};
        targetToPush.id = i;
        console.log(targetToPush.id);
        this.targets.push(targetToPush);
        console.log(this.targets);
      }
      return {};
    }

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.