Javascript Variable Assignment By Reference

Explaining Value vs. Reference in Javascript

A simple look at computer memory explains what’s happening

This article has been taken from Step Up Your JS: A Comprehensive Guide to Intermediate JavaScript, my online course. Feel free to view it there for interactive code playgrounds and an online quiz.

Javascript has 5 data types that are passed by value: , , , , and . We’ll call these primitive types.

Javascript has 3 data types that are passed by reference: , , and . These are all technically Objects, so we’ll refer to them collectively as Objects.

Primitives

If a primitive type is assigned to a variable, we can think of that variable as containing the primitive value.

var x = 10;
var y = 'abc';
var z = null;

contains . contains. To cement this idea, we’ll maintain an image of what these variables and their respective values look like in memory.

When we assign these variables to other variables using , we copy the value to the new variable. They are copied by value.

var x = 10;
var y = 'abc';var a = x;
var b = y;console.log(x, y, a, b); // -> 10, 'abc', 10, 'abc'

Both and now contain . Both and now contain . They’re separate, as the values themselves were copied.

Changing one does not change the other. Think of the variables as having no relationship to each other.

var x = 10;
var y = 'abc';var a = x;
var b = y;a = 5;
b = 'def';console.log(x, y, a, b); // -> 10, 'abc', 5, 'def'

Objects

This will feel confusing, but bear with me and read through it. Once you get through it, it’ll seem easy.

Variables that are assigned a non-primitive value are given a reference to that value. That reference points to the object’s location in memory. The variables don’t actually contain the value.

Objects are created at some location in your computer’s memory. When we write , we’ve created an array in memory. What the variable receives is the address, the location, of that array.

Let’s pretend that is a new data type that is passed by value, just like or . An points to the location, in memory, of a value that is passed by reference. Just like a string is denoted by quotation marks ( or ), an will be denoted by arrow brackets, .

When we assign and use a reference-type variable, what we write and see is:

1) var arr = [];
2) arr.push(1);

A representation of lines 1 and 2 above in memory is:

1.

2.

Notice that the value, the address, contained by the variable is static. The array in memory is what changes. When we use to do something, such as pushing a value, the Javascript engine goes to the location of in memory and works with the information stored there.

Assigning by Reference

When a reference type value, an object, is copied to another variable using , the address of that value is what’s actually copied over as if it were a primitive. Objects are copied by reference instead of by value.

var reference = [1];
var refCopy = reference;

The code above looks like this in memory.

Each variable now contains a reference to the same array. That means that if we alter , will see those changes:

reference.push(2);
console.log(reference, refCopy); // -> [1, 2], [1, 2]

We’ve pushed into the array in memory. When we use and , we’re pointing to that same array.

Reassigning a Reference

Reassigning a reference variable replaces the old reference.

var obj = { first: 'reference' };

In memory:

When we have a second line:

var obj = { first: 'reference' };
obj = { second: 'ref2' }

The address stored in changes. The first object is still present in memory, and so is the next object:

When there are no references to an object remaining, as we see for the address above, the Javascript engine can perform garbage collection. This just means that the programmer has lost all references to the object and can’t use the object any more, so the engine can go ahead and safely delete it from memory. In this case, the object is no longer accessible and is available to the engine for garbage collection.

== and ===

When the equality operators, and , are used on reference-type variables, they check the reference. If the variables contain a reference to the same item, the comparison will result in .

var arrRef = [’Hi!’];
var arrRef2 = arrRef;console.log(arrRef === arrRef2); // -> true

If they’re distinct objects, even if they contain identical properties, the comparison will result in .

var arr1 = ['Hi!'];
var arr2 = ['Hi!'];console.log(arr1 === arr2); // -> false

If we have two distinct objects and want to see if their properties are the same, the easiest way to do so is to turn them both into strings and then compare the strings. When the equality operators are comparing primitives, they simply check if the values are the same.

var arr1str = JSON.stringify(arr1);
var arr2str = JSON.stringify(arr2);console.log(arr1str === arr2str); // true

Another option would be to recursively loop through the objects and make sure each of the properties are the same.

Passing Parameters through Functions

When we pass primitive values into a function, the function copies the values into its parameters. It’s effectively the same as using .

var hundred = 100;
var two = 2;function multiply(x, y) {
// PAUSE
return x * y;
}var twoHundred = multiply(hundred, two);

In the example above, we give the value . When we pass it into , the variable gets that value, . The value is copied over as if we used an assignment. Again, the value of is not affected. Here is a snapshot of what the memory looks like right at the PAUSE comment line in .

Pure Functions

We refer to functions that don’t affect anything in the outside scope as pure functions. As long as a function only takes primitive values as parameters and doesn’t use any variables in its surrounding scope, it is automatically pure, as it can’t affect anything in the outside scope. All variables created inside are garbage-collected as soon as the function returns.

A function that takes in an Object, however, can mutate the state of its surrounding scope. If a function takes in an array reference and alters the array that it points to, perhaps by pushing to it, variables in the surrounding scope that reference that array see that change. After the function returns, the changes it makes persist in the outer scope. This can cause undesired side effects that can be difficult to track down.

Many native array functions, including and , are therefore written as pure functions. They take in an array reference and internally, they copy the array and work with the copy instead of the original. This makes it so the original is untouched, the outer scope is unaffected, and we’re returned a reference to a brand new array.

Let’s go into an example of a pure vs. impure function.

function changeAgeImpure(person) {
person.age = 25;
return person;
}var alex = {
name: 'Alex',
age: 30
};var changedAlex = changeAgeImpure(alex);console.log(alex); // -> { name: 'Alex', age: 25 }
console.log(changedAlex); // -> { name: 'Alex', age: 25 }

This impure function takes in an object and changes the property on that object to be 25. Because it acts on the reference it was given, it directly changes the object . Note that when it returns the object, it is returning the exact same object that was passed in. and contain the same reference. It’s redundant to return the variable and to store the reference in a new variable.

Let’s look at a pure function.

function changeAgePure(person) {
var newPersonObj = JSON.parse(JSON.stringify(person));
newPersonObj.age = 25;
return newPersonObj;
}var alex = {
name: 'Alex',
age: 30
};var alexChanged = changeAgePure(alex);console.log(alex); // -> { name: 'Alex', age: 30 }
console.log(alexChanged); // -> { name: 'Alex', age: 25 }

In this function, we use to transform the object we’re passed into a string, and then parse it back into an object with . By performing this transformation and storing the result in a new variable, we’ve created a new object. There are other ways to do the same thing such as looping through the original object and assigning each of its properties to a new object, but this way is simplest. The new object has the same properties as the original but it is a distinctly separate object in memory.

When we change the property on this new object, the original is unaffected. This function is now pure. It can’t affect any object outside its own scope, not even the object that was passed in. The new object needs to be returned and stored in a new variable or else it gets garbage collected once the function completes, as the object is no longer in scope.

Test Yourself

Value vs. reference is a concept often tested in coding interviews. Try to figure out for yourself what’s logged here.

function changeAgeAndReference(person) {
person.age = 25;
person = {
name: 'John',
age: 50
};

return person;
}var personObj1 = {
name: 'Alex',
age: 30
};var personObj2 = changeAgeAndReference(personObj1);console.log(personObj1); // -> ?
console.log(personObj2); // -> ?

The function first changes the property on the original object it was passed in. It then reassigns the variable to a brand new object and returns that object. Here’s what the two objects are logged out.

console.log(personObj1); // -> { name: 'Alex', age: 25 }
console.log(personObj2); // -> { name: 'John', age: 50 }

Remember that assignment through function parameters is essentially the same as assignment with . The variable in the function contains a reference to the object, so initially it acts directly on that object. Once we reassign to a new object, it stops affecting the original.

This reassignment does not change the object that points to in the outer scope. has a new reference because it was reassigned but this reassignment doesn’t change .

An equivalent piece of code to the above block would be:

var personObj1 = {
name: 'Alex',
age: 30
};var person = personObj1;
person.age = 25;person = {
name: 'john',
age: 50
};var personObj2 = person;console.log(personObj1); // -> { name: 'Alex', age: 25 }
console.log(personObj2); // -> { name: 'John', age: '50' }

The only difference is that when we use the function, is no longer in scope once the function ends.

That’s it. Go write some code.

— — — — — — — — — — — — — — — — — — — — — — — — — — — — — —

If this was useful, please hit the heart and feel free to check out my other work.

My Work

Online Course

I’ve created an online course on educative.io covering intermediate JavaScript topics such as scope, closures, OOP, , , //, asynchronous code, array and object manipulation, and ES2015+.

Step Up Your JS: A Comprehensive Guide to Intermediate JavaScript
This is for those familiar with the basics of JavaScript and looking to advance their knowledge and really understand…www.educative.io

Recent Articles

The Simple Rules to ‘this’ in Javascript

React Ecosystem Setup — Step-By-Step Walkthrough
Understand what React, Webpack, and Babel are doing and how to configure them yourself

Variable assignment is a concept that many developers find confusing. In this post I will try to explain how JavaScript treats variable assignments and argument passing.

Key to this is understanding the difference between primitive values and objects. In JavaScript there are five types of primitive values - undefined, null, boolean, string and number. As you will see from my examples, javaScript treats primitive values differently from objects. The most important difference is that primitive values are manipulated by value and objects by reference.

What this really means is that primitive values will not be shared between multiple variables – even after setting variables equal to each other. Every variable representing a primitive value is guaranteed to represent a unique memory locations and no two variables will ever point to the same memory location. The other key point is that the value itself is stored in the physical memory location.

Object variables are different since multiple variables may point to a shared location in memory instead of representing multiple copies of the same data. Unlike primitive values, objects are not immutable, so you have to be careful when changing referenced data since the change will be seen by all references.

In the following code samples I will show some practical examples of this.

To better visualize the concepts I have included my examples as Jasmine unit tests.

Assigning primitive values:

First up is assignment of primitive values. Initially we assign the value 'Joe' to the variable joe. Next we create another variable, alsoJoe, and assign it to joe. Not surprisingly we see that both variables contain the same value. However, it's important to point out that this assignment does not tie joe and alsoJoe together. In fact all that happened was that the value from joe was copied into alsoJoe, so when we go to change alsoJoe we don't have to worry about affecting joe. This is because the two variables are backed by two distinct memory locations – with no crossover.

Assigning object values

The next example is very similar, but instead of primitive types we will be working with object references.

Initially this may not seem that different from the previous example, but the key difference is that assigning person1 to person2 will join the two object variables at the hip. Meaning you now have two handles to the same data and a change via one will affect the other. The only way to break the link is to reassign one of the variables to a different object. Reassigning will point the variable to a new memory location, but the other variable will be unaffected since it still points to its original location.

Passing arguments by value

Next up is passing primitive values to functions.

Based on our previous discussion it may not come as a total surprise that the change made to val inside incrementValue() is not seen outside the function. Instead of incrementing to 20 the value remains at 10. This is because the primitive value is passed as an independent copy of the original value and changes to the copy will not be reflected in the original value.

Passing objects (Call by Sharing)

Next we will look at how this changes if we instead pass an object reference to our function.

As you can see from the test, adding 10 to the original value inside incrementObjectValue() is visible outside the function. This is because objects are not passed as copies, but as memory references. Similar to our object assignment example, the argument of the function points to the same memory location as the original myObj variable.

The term for this behavior in JavaScript is call by sharing.

Call by sharing means that the arguments of the called function point to the same memory locations as the variables passed by the caller. However, we are still dealing with two sets of independent memory pointers.

As we have seen, mutating the reference inside the function will be seen by the caller. However, since it's an independent reference pointer, reassigning the reference inside the function will break the connection between the caller and function. Meaning if you assign the argument to a new object reference inside the function, you will not see that change from the caller's side.

This behavior is the same as the examples above where we reassigned variables that initially pointed to the same locations in memory.

The reassigning behavior is a key difference between call by sharing and call by reference. In languages where pass by reference is used, reassignment of references will be seen by the caller as well.

Lastly I have included an example with a subtle twist where I am passing myObj.val instead of the entire object. This may seem similar, but it's actually different since it's going back to passing a primitive value. As you can see, the effects are the same as in the previous example – the argument is passed by value and no changes are visible outside the function.

Categories: 1

0 Replies to “Javascript Variable Assignment By Reference”

Leave a comment

L'indirizzo email non verrà pubblicato. I campi obbligatori sono contrassegnati *