ponyfoo.com

Variables declared using const are not immutable

Fix
A relevant ad will be displayed here soon. These ads help pay for my hosting.
Please consider disabling your ad blocker on Pony Foo. These ads help pay for my hosting.
You can support Pony Foo directly through Patreon or via PayPal.

Using const only means that the variable will always have a reference to the same object or primitive value, because that reference can’t change. The reference itself is immutable, but the value held by the variable does not become immutable.

The following example shows that even though the people reference couldn’t be changed, the array itself can indeed be modified. If the array were immutable, this wouldn’t be possible.

const people = ['Tesla', 'Musk']
people.push('Berners-Lee')
console.log(people)
// <- ['Tesla', 'Musk', 'Berners-Lee']

A const statement only prevents the variable binding from referencing a different value. Another way of representing that difference is the following piece of code, where we create a people variable using const, and later assign that variable to a plain var humans binding. We can reassign the humans variable to reference something else, because it wasn’t declared using const. However, we can’t reassign people to reference something else, because it was created using const.

const people = ['Tesla', 'Musk']
var humans = people
humans = 'evil'
console.log(humans)
// <- 'evil'

If our goal was to make the value immutable, then we’d have to use a function such as Object.freeze. Using Object.freeze prevents extensions to the provided object, as represented in the following code snippet.

const frozen = Object.freeze(['Ice', 'Icicle', 'Ice cube'])
frozen.push('Water')
// Uncaught TypeError: Can't add property 3, object is not extensible
Liked the article? Subscribe below to get an email when new articles come out! Also, follow @ponyfoo on Twitter and @ponyfoo on Facebook.
One-click unsubscribe, anytime. Learn more.

Comments (5)

sunpietro wrote

Object.freeze is almost a good way to go, if we want to turn a variable into immutable state, but … it can easily mutate, because Object.freeze freezes an object at one level. If you have nested objects, then nested object can be changed.

Nicolás Bevacqua wrote

Nothing prevents you from applying Object.freeze recursively, but that was beyond the point of the article.

const freeze = o => {
  if (o !== null && o !== undefined && typeof o !== 'string') {
    Object
      .keys(o)
      .forEach(key => freeze(o[key]))
  }
  return Object.freeze(o)
}
barbu wrote

Why checking only for string and not number? or boolean?

if (o !== null && o !== undefined && typeof o !== 'string') {
Nicolás Bevacqua wrote

I was specifically checking for strings as a hack, because 'a'[0] === 'a' means any strings would result in infinite recursion.

A better idea would be to keep a Set (you can read more about Set here) of "seen references" as a circuit-breaker, avoiding unwarranted recursion. We need to keep the null and undefined checks so that Object.keys doesn’t throw.

const freeze = (o, seen = new Set()) => {
  if (o !== null && o !== undefined) {
    Object
      .keys(o)
      .forEach(key => {
        if (seen.has(o[key])) {
          return
        }
        seen.add(o[key])
        freeze(o[key], seen)
      })
  }
  return Object.freeze(o)
}

I was lazy! 😅

Soji wrote

For immutable objects

const obj = {}; Object.defineProperty( obj, "propertyName", { value: "a value", writable: false, enumerable: true, configurable: true }); Object.seal(obj);