Psst: have a look at the JavaScript Console 💁

In JavaScript, strings, numbers and booleans are passed and copied by value:



    // start with strings, numbers and booleans

    var myNumber = 3;
    var myString = 'three';
    var myBool = true;

    var myCopiedNumber = myNumber;
    var myCopiedString = myString;
    var myCopiedBool = myBool;

    console.log( myCopiedNumber, myCopiedString, myCopiedBool ); // 3, "three", true

    // update the copies, and then check the original values for mutations

    myCopiedNumber = 4;
    myCopiedString = "four";
    myCopiedBool = false;

    console.log( myCopiedNumber, myCopiedString, myCopiedBool ); // 4, "four", false

    console.log( myNumber, myString, myBool ); // 3, "three", true

    

Arrays behave differently; let's look at an example:



    // Let's say we have an array

    const players = ['Wes', 'Sarah', 'Ryan', 'Poppy'];

    // and we want to make a copy of it.
    // You might think we can just do something like this:

    const team = players;

    console.log( team ); // ["Wes", "Sarah", "Ryan", "Poppy"]

    // but what happens when we update the new array?

    team[3] = "Roxi";

    console.log( team );    // ["Wes", "Sarah", "Ryan", "Roxi"]
    console.log( players ); // ["Wes", "Sarah", "Ryan", "Roxi"]

    // oh no - we have edited the original array too!

    

Arrays and objects in JavaScript are passed by reference. So how do we safely copy an array so that we can avoid unwanted mutations? There are several methods specific to arrays, including slice(), from(), concat(), and ES2015's spread operator.



    const myTeam = ["Michael", "James", "Roxi"];

    const teamTwo = myTeam.slice();

    const teamThree = Array.from(myTeam);

    const teamFour = [].concat(myTeam);

    const teamFive = [...myTeam];

    console.log(teamTwo, teamThree, teamFour, teamFive); // same result 4 times: ["Michael", "James", "Roxi"]

    teamTwo[2] = "Bud";

    teamThree[2] = "Petey";

    teamFour[2] = "Sassy";

    teamFive[2] = "Patch";

    console.log(myTeam, teamTwo, teamThree, teamFour, teamFive);

    // ["Michael", "James", "Roxi"] ["Michael", "James", "Bud"] ["Michael", "James", "Petey"] ["Michael", "James", "Sassy"] ["Michael", "James", "Patch"]

    

The original array was not mutated, because it was copied and not referenced. Objects behave similarly, but copying them is somewhat different:



    // Let's say we have a person object

    const person = {
        name: "Michael",
        occupation: "College professor"
    };

    // and then we try to copy it using the assignment operator

    const michael = person;

    // change a property on the 'copied' object

    michael.occupation = "Web Dev";

    // and log the result

    console.log( person.occupation ); // "web Dev"

    // So how do copy an object instead of referencing it?
    // The spread operator is not available for objects yet
    // so {...person} will not work.  We have to use the assign() method.

    const me = {
        name: "Michael",
        occupation: "Tailor"
    };

    const newMe = Object.assign({}, me, {occupation: "Web Dev"});

    console.log(me); // Object {name: "Michael", occupation: "Tailor"}
    console.log(newMe); // Object {name: "Michael", occupation: "Web Dev"}

    

It is important to note that with Object.assign(), the order of the parameters is signifigant. The method returns an object that starts with the leftmost parameter, merges in the second, and then (in order), merges in any additional objects, overwriting common keys at each step.

If you are familiar with React or JSX, you may be used to seeing the spread operator used with objects (i.e. {...props}), but this is not implemented in browsers yet.

Another thing to keep in mind is that all of the above techniques make shallow copies. In other words, they don't work with multidimensional arrays or nested objects in the same way that they work with top level arrays or objects, but instead return to referencing anything lower than the top level.

One more thing that I found counter-intuitive: objects passed to functions are passed by reference as well, so making changes to a property of a local object will change the original object even though functions create a new scope.



    const worker = {
        name: "Michael",
        occupation: {
            name: "Web Dev",
            startDate: 2011
        }
    };

    function test(obj) {
        obj.startDate = 2015;
        console.log('// TEST FUNC');
        console.log(obj);
        console.log('// TEST FUNC');
    }

    test( worker );

    // TEST FUNC
    // Object { name: "Michael", occupation: Object { name: "Web Dev", startDate: 2015 } }
    // TEST FUNC

    console.log(worker); // Object { name: "Michael", occupation: Object { name: "Web Dev", startDate: 2015 } }

    

We leave off with a #hacky way to make deep clones by serializing and immediately unserializing objects using the native JSON functions. Use at your own risk!



    const worker = {
        name: "Michael",
        occupation: {
            name: "Web Dev",
            startDate: 2011
        }
    };

    const anotherWorker = JSON.parse(JSON.stringify(worker));

    anotherWorker.occupation.startDate = 2015;

    console.log(worker); // Object { name: "Michael", occupation: Object { name: "Web Dev", startDate: 2011 } }

    console.log(anotherWorker); // Object { name: "Michael", occupation: Object { name: "Web Dev", startDate: 2015 } }