Meeting Lambda #8: To Mutate or Not

Lubien
Lambda JS
Published in
3 min readJun 26, 2019
Brazil — PA — Belém — Docks Station. Photo by Cayambe. Art by Rian Vergara.

[Table of Contents] [Previous] [Next]

One important technique every JavaScript developer should have is to distinguish between values and references. The reason why is because if you fail to understand references, your code eventually can lead to unexpected behaviors.

As you may notice, the first part works as expected. y receives the current value of x unlike ys that receives a reference of xs. In the case of the array, both identifiers are different (a.k.a. variable name — xs and ys) but they point to the same variable in the memory — same reference.

To prevent this kind of behavior you should properly copy all elements from the array.

Another way to do this is to simply use Array#slice with empty arguments (See Meeting Lambda #7: Slicing) since slice always generates a new list (and it does it pretty fast).

The problem with this approximation is that it doesn’t copy nested arrays.

Later in this article we’ll see how to solve this.

Looping with Array#forEach

Different from map, forEach was not designed to build a new list. You may be tempted to do something like this:

As said before, like map callback, forEach callbacks receive as third argument a reference to the original array. You can use it to either read or modify it at your own risk. Keep in mind that once you modify the original list, you lose your previous values. If you want an array of transformed values without losing the original ones use map.

Objects

Another data structure in JavaScript that’s passed as reference is object.

A neat trick to copy object properties is to use Object.assign with the first param as an empty object.

What happens with Object.assign is that lubien properties (second argument) will be copied to the new empty object (first argument). Since it’s a new object you wont be messing with references from anywhere.

Object keys can have any kind of values. Integers, strings or even… Objects. That’s where our problem begins.

As you can see, Object.assign doesn’t deeply copy nested objects, only the outermost one. The same is valid for arrays:

True Cloning

To get true deep cloning of values (even objects and arrays) we must create a helper function named deepClone:

What deepClone do is loop over all keys from the source object obj and:

  • If it’s an array, clone via .map(deepClone) since map creates a new array and deepClone will recursively clone elements;
  • If it’s an object, clone all properties via a recursive call of deepClone;
  • Otherwise return the value (since it should not be a reference).

Mutations

You mutate a value when you take an already existing variable and changes it someway. You mutate an array when you add elements or change an element value. You mutate an object when you create or change properties.

Mutation by itself is not a problem. The problem begins when your mutations are causing unexpected side effects like the previous cases in this story.

To mutate or not is a question programmers should ask themselves while working with any kinds of variable references. Perhaps you don’t want a new array, so there should be no problem mutation all these values or perhaps you feel more comfortable not mutating anything at all. That’s a matter that ultimately comes down to yourself and your development team.

Review

  1. Arrays and Objects are passed as references.
  2. Mutating references can lead to unexpected behaviors.
  3. You should clone values instead copying references of Arrays and Objects.
  4. Deep cloning objects is tricky and expensive.

Links

--

--

Lubien
Lambda JS

Web developer that loves to make JoJo references