Start Teaching
Messages
User
Back to Stories
A Tale of Two Copies
Technology
  • Dec 7, 2020
  • 10 minutes

A Tale of Two Copies

Let’s face it — Javascript is a funny language, to say the least. And the internet is filled with articles articulating this. But it’s simple, powerful, and ubiquitous, and learning a trick or two wouldn’t go amiss. In this article, we’ll be discussing a tricky bit in Javascript — copying.


Consider two variables a and b. You have a value stored in a, which you wish to copy into b. This can be done as follows


const a = 'Hello';

const b = a;


Now you might be wondering what was the point of showing that, for that was obvious. Bear with me. This method holds true so long as a and b are of primitive data types — string, number, boolean, etc. The above bit of code will copy the value stored in a to b and then, changes in one won’t affect the other. Again, obvious.


const a = 10;

const b = a;

b += 10;


Let’s take this one step ahead now. What if a was say, an array? “Yea, what about it?”, I hear you ask. Logic dictates that this should not make any difference. The array in a can be copied into b in exactly the same way and changes toa won’t affect b and vice versa.


const a = [1, 2, 3, 4];

const b = a; 


console.log(b); // Result: [1, 2, 3, 4] 


a.push(5);

console.log(a); // Result: [1, 2, 3, 4, 5]

console.log(b); // Result: [1, 2, 3, 4, 5] 


b.push(6);

console.log(b); // Result: [1, 2, 3, 4, 5, 6]

console.log(a); // Result: [1, 2, 3, 4, 5, 6]


Surprised? I’m sure that at least a couple of you reading this would certainly be. Feel free to try it out and see it for yourself.


So, what happened there?

This behavior is a result of the way Javascript works. In JS, Arrays, Objects, and Functions(let’s collectively call them Objects, because that’s how JS sees all 3 of them), are treated differently from String, Number, Boolean, Null, and Undefined(the Primitives). While the Primitives are passed by value, Objects are passed by reference.


This essentially means that when we assign a Primitive variable to another Primitive variable, like in our simple copy case, the value stored in one variable is copied into the other and after this copying, they are independent of each other. On the other hand, in the case of Objects, when you assign an Object variable to another Object variable, both variables are essentially referring to the same Object in memory — just as how Christiano Ronaldo and CR7 refer to the same individual.


It’s because of this pass by reference nature of objects that we saw the arrays from the above code snippet behave the way did. When we work on a or b, we are effectively working on the same array, referred to by using different names.


It’s also worth noting that JS is not exclusive in this behavior — Typescript, which is essentially a superset of JS, shows the same behavior. So does Python.


What’s the solution?

Time and again, situations might arise when you need to copy an Object-type variable and not end up altering the source variable while working on the copy and vice versa.


Enter Spread …

Introduced in ECMAScript 6(ES6), the spread operator(…) solves this very problem. Using the spread syntax, you can extract the value of an Object, which can subsequently be copied into another variable.


const a = [1, 2, 3, 4];

const b = [...a]; 


console.log(b); // Result: [1, 2, 3, 4] 


a.push(5);

console.log(a); // Result: [1, 2, 3, 4, 5]

console.log(b); // Result: [1, 2, 3, 4] 


b.pop();

console.log(b); // Result: [1, 2, 3]

console.log(a); // Result: [1, 2, 3, 4, 5]


As can be seen from the above code snippet, once we use the spread syntax to copy Objects, changes in one variable don’t affect the value stored in the other, and vice versa. What has essentially happened here is that by extracting and copying the value of a to b, we ensured that both a and b are not referring to the same object in memory.


Yay! We’re sorted — not so fast

As much as spread operator is a lifesaver as far as copying Objects go, there is a catch and a big one at that. Consider the following piece of code.


const johnMarks = [100, 98, 95, 99];

const a = { 

  name: 'John Smith', 

  marks: johnMarks

};

const b = {...a}; 


console.log(a); // { name: 'John Smith', marks: [100, 98, 95, 99] };

console.log(b); // { name: 'John Smith', marks: [100, 98, 95, 99] }; 


johnMarks.push(100); 


console.log(a); // { name: 'John Smith', marks: [100, 98, 95, 99, 100] };

console.log(b); // { name: 'John Smith', marks: [100, 98, 95, 99, 100] };


Looks like we still have problems after all.


As you can see, despite using the spread operator to copy the value of a to b, changes to johnMarks affected the marks field for both a and b. This happens because the copying done by means of the spread operator is what is known as a ‘shallow copy’.


Shallow Copy is one where while copying an Object, we copy values of the main Object but pass the reference of Objects within it to the copy. Thus, in our case, both a and b store a reference to the same johnMarks Object. What we need in such cases, is a ‘deep copy’.


In a deep copy, values of the Object, as well as those of Objects within it are copied out, meaning that if we deep copy a to b, the marks field will hold the value of johnMarks and not a reference to it.


How to perform a deep copy?

While various ways exist to perform a deep copy in Javascript, including creating a custom function that could maybe recursively do a shallow copy to emulate a deep copy, I usually go with a simple yet effective method. All we need are two methods already available to us out of the box.


const johnMarks = [100, 98, 95, 99];

const a = { 

   name: 'John Smith', 

   marks: johnMarks

};

const b = JSON.parse(JSON.stringify(a)); 


console.log(a); // { name: 'John Smith', marks: [100, 98, 95, 99] };

console.log(b); // { name: 'John Smith', marks: [100, 98, 95, 99] }; 


johnMarks.push(100); 


console.log(a); // { name: 'John Smith', marks: [100, 98, 95, 99, 100] };

console.log(b); // { name: 'John Smith', marks: [100, 98, 95, 99] };


And there you have it — a neat deep copy performed by combining the JSON.parse() and JSON.stringify() methods. What is happening here is that first a string is constructed out of the source object, which is then used to create a new Object that gets stored in the second variable.


Note that while using this method for deep copying, ensure that the objects involved conform to JSON conventions, to avoid unexpected errors. If they do not, you may have to look at other methods such as perhaps a custom function, as already mentioned.



That’s it then. In this article, we looked at the need for copying in Javascript, two different copying styles and a method each for implementing the copying styles. The methods illustrated and/or mentioned are by no means an exhaustive list, but simply methods I personally favour.


Recommended instructors

Find instructors for your goals & budget.