1

For example I have listings array :

[ { name: 'bob', price: '10' }, { name: 'jack', price: '12' } ]

I am trying to find lowest seller and work with it's data later. I do var currentLowestSeller = listings[0];

Now currentLowestSeller is:

{ name: 'bob', price: '10' }

Later I do changes is currentLowestSeller but I don't want main listings array to be changed. I do currentLowestSeller.price = currentLowestSeller.price * 0.5; and after this listings array looks like this:

[ { name: 'bob', price: 5 }, { name: 'jack', price: '12' } ]

How do I prevent this?

If you wanna recreate this just run this code:

var listings = [];

var name1 = 'bob';
var name2 = 'jack';
var price1 = '10';
var price2 = '12';

listings.push({
  name: name1,
  price: price1
})

listings.push({
  name: name2,
  price: price2
})

var currentLowestSeller = listings[0];
currentLowestSeller.price = currentLowestSeller.price * 0.5;

console.log(listings);

What I have tried:

I tried creating a copy of listings array before doing anything.

var unchangedListings = listings;
var currentLowestSeller = listings[0];
currentLowestSeller.price = currentLowestSeller.price * 0.5;

console.log(unchangedListings);

But it didn't work.

Later I decided that const unchangedListings = listings; would help. But for some reason it also changes a value defined as a constant.

narra_kk
  • 111
  • 6
  • 3
    Does this answer your question? [What is the most efficient way to deep clone an object in JavaScript?](https://stackoverflow.com/questions/122102/what-is-the-most-efficient-way-to-deep-clone-an-object-in-javascript) – Rashomon Dec 01 '20 at 14:37
  • 3
    "I hate JavaScript." Javascript certainly has its issues, but very few languages feature collections that are immutable by default. Python, Java, Ruby, C++, etc all use reference types for collections and objects, and you'd be having the same "problem". This is just part of programming. – Jared Smith Dec 01 '20 at 14:39
  • Also per your edit about using const: const only prevents rebinding the name, const references are still mutable. `const foo = {}; foo.a = 1;` – Jared Smith Dec 01 '20 at 15:28

4 Answers4

2
var unchangedListings = listings;

This means, unchangedListings is indicating to the value of the listings, so if you change the unchangedListings value, it means that you are also updating the listings.

To prevent this, you need to clone the value. You should deep clone the object.

var currentLowestSeller = JSON.parse(JSON.stringify(listings[0]))

or

var currentLowestSeller = Object.assign({}, listings[0])
wangdev87
  • 8,611
  • 3
  • 8
  • 31
  • what do you mean, "shallow copy"? @JaredSmith – wangdev87 Dec 01 '20 at 14:42
  • Using spread that way does not deep clone: `const foo = [{}, {}]; const bar = [...foo]; foo[0].a = 1; console.log(bar[0].a); will print 1. I used arrays in my example but same holds true for object spread: the object at the zeroth position of foo and bar is the same object, it did not get cloned. – Jared Smith Dec 01 '20 at 14:43
  • 2
    got your point, i updated answer, @JaredSmith – wangdev87 Dec 01 '20 at 14:48
  • +1 Another nitpick based on your edit: that only works for things that are JSON serializable. There are lots of things in Javascript that aren't (e.g. functions). – Jared Smith Dec 01 '20 at 14:49
2

Origin of the problem

The behaviour you are seeing is common to most languages, and not related to javascript.

Arrays only contain references to the objects they contain. Extracting a key from an array (or an object for that matter) does not make copies of the value of the key. If that was the case there would be no way to perform any change in the state of the program.

var a = { toto: 1 };  // create object
var b = a;            // b is pointing the the same object
b['toto'] = 2;        // update the object (there is only one)

console.log(a == b);  // true because a and b are the SAME object (not just equal,
                      // both a and b point to the same place in the computer memory)

console.log(a);       // { toto: 2 } both objects have been edited

If you need to manipulate an object, without modifying the original, you need to explicitly make a copy.

However, when using nested objects or nested arrays a problem will occur. Do you need a "deepcopy" or a "shallow copy"?

Shallow copy

A shallow copy means copying only the "first level".

var a = { toto: 1, tata: { tutu: 1 } };
var b = { ... a }; // make a "shallow copy"

// We change "b", did "a" change? => No
b.toto = 2;
console.log(a); // { toto: 1, tata: { tutu: 1 } }
                // "a" was not modified!

console.log(b); // { toto: 2, tata: { tutu: 1 } }
                // "b" was modified!

// we change a nested object in "b", did "a" change? => Yes
b.tata.tutu = 2;
console.log(a); // { toto: 1, tata: { tutu: 2 } }
                // "a" was modified!

console.log(b); // { toto: 2, tata: { tutu: 2 } }
                // "b" was modified!

Deep copy

A deep copy will make a copy of all nested arrays and objects (and comes with a significant cost in performance).

Javascript does not come with a language built-in to perform deep copies, because it is not a common operation, and is expensive.

The most common way to perform deep copies of objects is to use the JSON built-ins, but many ways exist with different pros and cons (for instance, using the JSON builtins is fast, but will break if your objects contain NaN or Date instances).

Check this thread for more info: What is the most efficient way to deep clone an object in JavaScript?

var a = { toto: 1, tata: { tutu: 1 } };
var b = JSON.parse(JSON.stringify(a)); // make a "deep copy"

// a and b are now completely different, they share nothing in memory
// we can edit any subobject, they will not be any consequence between them.
a.tata.tutu = 2;
Eloims
  • 5,106
  • 4
  • 25
  • 41
1

In case the list or dictionary is nested, you could use clone from the Ramda library:

import { clone } from 'ramda';

var currentLowestSeller = clone(listings[0]);

You can find more information here: https://medium.com/javascript-in-plain-english/how-to-deep-copy-objects-and-arrays-in-javascript-7c911359b089, they explain very well the difference between shallow and deep copies.

chococroqueta
  • 694
  • 1
  • 6
  • 18
  • is it any better then `const currentLowestSeller = JSON.parse(JSON.stringify(listings[0]));` ?? – narra_kk Dec 01 '20 at 14:46
  • @narra_kk yes it's better, that method you mention only works for things that are JSON serializable. There are lots of things in Javascript that aren't (e.g. functions). – Jared Smith Dec 01 '20 at 14:47
1

One of the most important concepts of Javascript (and many other languages) is the concept of reference types. Javascript has 3 data types that are passed by reference: Array, Function, and Object. Since it is really important to understand this, I would recommend reading this article.

In your case:

var unchangedListings = listings; // still points to listings
var currentLowestSeller = listings[0]; // changes listings

It is always a good practice to copy arrays before mutating them:

const currentLowestSeller = [... listings]; // currentLowestSeller points to a new array
FelHa
  • 1,043
  • 11
  • 24