Value vs Reference - Javascript Concepts 3

Posted on January 30, 2019

This is part of a series where I try to explain through each of 33 JS Concepts.

This part corresponds to the Pass by Value vs Pass by Reference.

Before we go into JavaScript of things, let’s look at what Pass by Value and Pass by Reference actually means.

Quoting this answer on stackoverflow,

  • When a parameter is passed by reference, the caller and the callee use the same variable for the parameter. If the callee modifies the parameter variable, the effect is visible to the caller’s variable.

  • When a parameter is passed by value, the caller and callee have two independent variables with the same value. If the callee modifies the parameter variable, the effect is not visible to the caller.

In essence, when you are passing a variable by reference, you are allowing a function to change the variable and hence bear the weight of side effects whatever that function did to your variable.

When passing by value, you give the function much lesser control. You will take into account only what is returned by the function. They can change the variables you pass in but that will not affect your variables.

But this concept is mostly outdated today. It is taught in colleges and for introductory classes but most modern languages choose to implement this way differently. Talking about modern languages, so does JavaScript.

JavaScript implements this concept with two types of data types: Primitives and Objects.

Instead of introducing two ways of passing variables to functions, we have two types of variables. The type of variable defines whether it is passed by value or by reference.

Primitives

There are 6 primitive data types in JavaScript:

  1. string
  2. number
  3. boolean
  4. null
  5. undefined
  6. symbol

These data types are represented at the lowest level and are immutable.

Immutability means that their properties cannot be altered at run time. Like,

1let stringVar = 'this is one long string';
2stringVar[4] = 'v'; // This is not possible
3stringVar = 'this is another string'; // This is not barred by the datatype

But I thought JavaScript did not have types

Yes, JavaScript is a loosely typed language. This still means that JavaScript has data types. But these data types are not bound to any variable.

1let variable = 'one';
2variable = 1;
3variable = true; // Totally fine executions

How does this relate to Value vs Reference?

Primitives are always passed by value in the truest form.

1function crazy(data) {
2 data = '_*.!@!!@(U!())'; // let this function do what it ever it wants, it wouldn't hurt out primitive
3}
4
5const impData = 'this is data as a string';
6crazy(impData); // rest assured, impData (primitive) is safe from manipulation by crazy()

Objects

Objects are the second kind of data type available in JavaScript.

If you are wondering where are Arrays, Maps and all those wonderful things, they are all just objects with special methods underneath.

Let’s define an object named Person:

1const person = {
2 name: 'John',
3};

This is how the structure would look in the memory.

personnameJohn

As you can see, { name: 'John' } is allocated a place in the memory and variable person is pointing to it.

Now, John has taken a role in life and is being reassigned.

1const person = {
2 name: 'john',
3};
4const developer = person; // John has become a developer.

Let us look at the memory representaion for this change:

Developer and Person

Now, we have a second variable developer pointing at the same memory that person did.

So, let’s say developer learns a new skill, he adds it to his skill array. And magically person variable would have learned this skill too. Because both these variables share the same object in memory.

1const person = {
2 name: 'john',
3 skills: ['hiking'],
4};
5const developer = person;
6developer.skills.push('coding');
7/* == comparison operator for objects just compares references */
8console.log(person === developer); // true

What if a new person now joins the company and is also named ‘John’?

1const person = {
2 name: 'john',
3 skills: ['hiking'],
4}
5const person2 = {
6 name: 'john2',
7 skills: ['hiking'],
8}
9person.skills.push('coding');
10/* Should person2 learn coding just because he has the same portfolio as the other John? */
11console.log(person === person2) // false, even though they share the same reference, they point at different memory instances and are obviously two different objects.

So, it is not the properties that matter, it is the memory it points to.

Everything that is an object (objects, arrays, functions, maps) are passed by reference in JavaScript. Going back to our earlier crazy example,

1function crazy(impData) {
2 impData.data = '_*.!@!!@(U!())'; // your important data just got crazified
3}
4
5const impData = {
6 data: 'suppper secret',
7};
8crazy(impData);
9console.log(impData); // gone. changed to gibberish by crazy.

How would I protect my objects from crazy functions?

1. Write lesser crazy functions. More Pure functions.

Pure functions are those that do not produce side effects. They interact only with their arguments and do not change them in anyway.

These functions produce result only as their return value.

1function sum(a, b) { // Pure function
2 return a+b;
3}
4function addValue(obj) { // Impure function
5 obj.value = 100;
6}

But what if it is not your function? What if you are passing the object to a third party?

2. Spread it.

There is a ECMAScript Stage 4 proposal for using spread operator for objects available here. You can use it now with a Babel Plugin

1function addValue(obj) { // Impure function
2 obj.value = 100;
3}
4
5const impObj = {
6 value: 10,
7}
8addValue({...impObj});

What you have essentially done here is to create a shallow copy of your impObj. Now the addValue can no longer hurt it by altering it’s properties. You can think of it like deploying a replica.

There is also a less fancy way of doing this with Object.assign

But as you might have figured from the word shallow there are issues with cloning like this.

1function doCrazy(obj) { // Impure function
2 obj.name = "Hehe"; //No effect
3 obj.skills.push("another"); // That seems to be breaking the illusion
4}
5
6const person = {
7 name: 'John',
8 skills: ['hiking']
9}
10doCrazy({...person});
11console.log(person);

Everything pointing to one

By building a shallow clone we have only eliminated the possibility of crazy people meddling with the first level of your object. The levels below it are still references and can be manipulated/changed by other functions/entities.

3. Deepclone it.

The next solution is to take the object clone it and go deeper and deeper into the object, find them clone them too.

I don’t know who you are, but if you are reference, I would clone you.

Luckily, there is a function to do that, cloneDeep.

Does this change the way I write code?

Well, it should. It should tell you why pure functions are so important in functional programming. It should tell you there are primitives and objects. and it should tell you how JavaScript implements Value vs Reference.

Is there something I missed? Something wrong? Something good? Ping me on Twitter