Mutant Ninja References (vs Copies)
mzakzook
Posted on November 12, 2019
Are your functions pure? Mine weren't. I had developed a less-than-desirable habit of creating reference variables when I should be creating array/object copies. What do you expect to see printed to your console for the following code?
let arrayOne = [1, 2, 3];
let arrayTwo = arrayOne;
arrayTwo[1] = 10;
console.log(arrayOne === arrayTwo); // -> ?
If you guessed 'true', you are correct. The variable 'arrayTwo' is simply a pointer to the memory location of 'arrayOne', which is why modifying 'arrayTwo' mutates 'arrayOne'.
Mutations can be cool, especially when they involve crime-fighting turtles and large amounts of pizza, but it's best to avoid them as much as possible with our code. This is an issue that is only of concern with memory-accessed variables (like arrays and objects). Here is another example where a reference variable can pose problems...
function mutateTurtle(turtle) {
turtle.superpowers = true;
turtle.name = `Super ${turtle.name.split(" ")[1]}`;
return turtle;
}
let regularRaphael = {
name: 'Powerless Raphael',
superpowers: false
};
let superRaphael = mutateTurtle(regularRaphael);
console.log(regularRaphael); // -> ?
console.log(superRaphael); // -> ?
The above function does not adhere to 'pure function' conventions because it mutates an outside variable, 'regularRaphael'. The two console logs above will print the same object:
{name: "Super Raphael", superpowers: true}
We don't want to forget about regular Raphael entirely - I'm sure there are aspects of his pre-super life that are worth remembering. It is usually best to make a copy of the object or array you are modifying. Let's refactor the above code to make our function 'pure':
function mutateTurtle(turtle) {
let superTurtle = JSON.parse(JSON.stringify(turtle));
superTurtle.superpowers = true;
superTurtle.name = `Super ${turtle.name.split(" ")[1]}`;
return superTurtle;
}
let regularRaphael = {
name: 'Powerless Raphael',
superpowers: false
};
let superRaphael = mutateTurtle(regularRaphael);
console.log(regularRaphael); // -> ?
console.log(superRaphael); // -> ?
Here is what is printed this time:
{name: "Powerless Raphael", superpowers: false}
{name: "Super Raphael", superpowers: true}
We avoided mutating regularRaphael by making a copy of him. By first converting regularRaphael's object to a string, using 'JSON.stringify', then parsing that string back to a JSON object, using 'JSON.parse', we created an object with the same keys/values, but with a new memory location. This 'parse/stringify' trick should also work with nested arrays/objects.
Because our original 'regularRaphael' object did not contain deep nesting we also could have achieved our desired outcome by using the spread operator...
let superTurtle = {...turtle};
Or the 'Object.assign' method...
let superTurtle = Object.assign({}, turtle);
Now that I'm aware of how to make copies opposed to references, I'll leave the mutating to the turtles and fight to keep regular Raphael's memory alive.
Sources:
- Explaining Value vs. Reference in Javascript, by Arnav Aggarwal
- Different methods to copy an object in JavaScript, by Juned Lanja
- TMNT Cover Image
Posted on November 12, 2019
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.