Sunday, 19 July, 2015 UTC


Summary

JavaScript does not give you full access to your data structures in memory. However, reference types still exist in the language. Mixing value and reference types comes with unwanted side-effects and bugs. Understanding the difference between value and reference types plays a vital role in writing robust programs.
This article is an entry-level introduction to value and reference types in JavaScript. It will lay down the foundations for the upcoming article on different cloning techniques.

Value and Reference types

Numbers, booleans, strings, null and undefined are primitive types. All primitive types are passed by value. Objects, arrays, and functions are passed by reference.
Strings are somewhat special in JavaScript. Unlike many other languages, strings are not defined as arrays of characters. What is more, the character type does not even exist in JavaScript. Strings are immutable data types, with an interface that makes them look like an array of characters. In practice, any modifications to a string result in a new, immutable string value.
After the introduction of value and reference types, let’s continue with a riddle. Determine the result of the console logs.
var people = [
    { name: 'John Hill', age: 22 },
    { name: 'Jack Chill', age: 27 }
];

var getInitials = function( name ) {
    // Reusing the name argument makes little sense in general.
    // We are making this assignment here for demonstrating
    // the difference between value types and reference types.
    name = name.split( ' ' ).map( function( word ) {
        return word.charAt( 0 );
    } ).join( '' );

    console.log( name );

    return name;
}

var increaseAge = function( person ) {
    person.age += 1;
}

var addPerson = function( people, name, age ) {
    people.push( { name: name, age: age } );
}

// Part 1: getInitials
console.log( getInitials( people[0].name ) ); 
console.log( people[0].name );

// Part 2: increaseAge
increaseAge( people[1] );
console.log( people[1].age );

// Part 3: addPerson
addPerson( people, 'Jim Gordon', 32 );
console.table( people );
Question: what is logged to the console?
Answer:
  • In the first part, the initials of a name are constructed by the getInitials function. The string value passed to the function is copied. This copy is accessed and modified inside the function. These modifications have nothing to do with the original value people[0].name.
  • The increaseAge function accepts an object. As objects are reference types, only the reference to the passed person is copied. When members of the referenced object are changed, the changes are kept even after executing the function. These changes are accessible via each reference.
  • Arrays are also reference types. Adding an element to an array is a permanent change.
JH
JH
John Hill
28
(index) name         age
0       "John Hill"  22
1       "Jack Chill" 28
2       "Jim Gordon" 32
undefined
Final notes:
  • console.table is a nice logging utility. It displays arrays of objects in tabular format in modern browsers like Google Chrome.
  • The last undefined is the return value of the last console.log. It also appears as a value in the console.

Mistake: treating a value type as a reference type

Suppose that the increaseAge method was erroneously implemented like this:
function increaseAge( age ) {
    age += 1;
}

console.log( 'Before:', people[0].age );
increaseAge( people[0].age );
console.log( 'After:', people[0].age );
The age stays 22 even after executing the increaseAge function. Primitive types are passed by value. Changing the copied value has no effects on the original value. Always encapsulate the fields you would like to change with an array or an object.
Another example is an erroneous attempt to replace a person with another one. Suppose that the replacePerson function accepts 3 arguments: the person to be replaced, and the name and age of the new person.
function replacePerson( person, name, age ) {
    person = { name: name, age: age };
}

replacePerson( people[1], 'Jack Newtown', 35 );
console.table( people );
This function does not do anything to the people object as the person argument was assigned a new value. The people data structure stays intact. The person handle inside the function references a completely new object after the assignment. Once this function terminates, the new object is thrown away.

Mistake: treating a reference type as a value type

Suppose that a customer enters a supermarket and buys a chewing gum. The customer was also a diligent collector of empty bottles and happened to cash in 4000 bottles in exchange for a 1000€ voucher. The cashier is checking if there are enough funds to accept the payment. This check is performed by the canChange function returning a boolean result:
var shopTransaction = {
    items: [ { name: 'Astro Mint Chewing Gum' } ],   
    price: 1,
    amountPaid: 1000
}

var cashier = {
    units:    [500, 200, 100, 50, 20, 10, 5, 2, 1],
    quantity: [0, 0, 5, 4, 5, 10, 10, 20, 9]
}

function canChange( shopTransaction, cashier ) {
    var amount = shopTransaction.amountPaid - shopTransaction.price;
    for( var i = 0; i < cashier.units.length; ++i ) {
        var unit = cashier.units[i];
        while ( amount >= unit && cashier.quantity[i] > 0 ) {
            amount -= unit;
            cashier.quantity[i] -= 1;
        }
    }

    return amount == 0;
}

console.log( canChange( shopTransaction, cashier ) );
console.log( cashier.quantity );
Although the function returns the value we expect, there is still a problem. The side effect of executing the canChange function is that all the notes disappeared from the cashier. Why?
The problem is that cashier is a reference type. All changes to fields inside the cashier are preserved even after exiting the function.
A possible solution is to create a new variable and equate it to cashier.quantity[i]. This will copy the value of cashier.quantity[i] to a new location in memory as we are dealing with a primitive type.
function canChange( shopTransaction, cashier ) {
    var amount = shopTransaction.amountPaid - shopTransaction.price;
    for( var i = 0; i < cashier.units.length; ++i ) {
        var unit = cashier.units[i];
        var currentQuantity = cashier.quantity[i];
        while ( amount >= unit && currentQuantity > 0 ) {
            amount -= unit;
            currentQuantity -= 1;
        }
    }

    return amount == 0;
}
In theory, it is also possible to deeply clone all reference types in functions that are not supposed to mutate their values. Deep cloning is easier said than done. We will explore the different types of cloning in the next article.

Summary

Primitive types are passed by value. Arrays, objects, and functions are passed by reference. If you ever find yourself mixing the two, review the above examples. Associate the story told by the examples with the different types of arguments a function can have. In order to avoid making mistakes, keep practicing until these concepts become natural to you.