Understanding Mutability of const Objects in JavaScript: The Distinction Between References and Assignments

Dec 04, 2025 · Programming · 11 views · 7.8

Keywords: JavaScript | const | Reference Types | Immutability | Object.freeze

Abstract: This article provides an in-depth analysis of the behavior of the const keyword in JavaScript, explaining why the contents of constant objects and arrays can be modified while the variable name itself cannot be reassigned. Through examination of ES6 specifications, memory models of reference types, and practical code examples, it clarifies that const only ensures immutable binding rather than immutable object contents. The article also discusses the Object.freeze() method as a solution for achieving true immutability and contrasts the behavior of primitive types versus reference types in constant declarations.

Introduction

With the widespread adoption of ECMAScript 6 (ES6), the const keyword has become a common feature in JavaScript development. Many developers encounter what appears to be a contradiction: arrays or objects declared with const can have their contents modified through method calls, while the variable name itself cannot be reassigned. This article systematically examines the mechanisms behind this phenomenon from three perspectives: language specifications, memory models, and practical applications.

Basic Semantics of the const Keyword

According to the ECMAScript specification, constants declared with const have two core characteristics: they cannot be changed through reassignment, and they cannot be redeclared in the same scope. This means the following operations will result in errors:

const x = 5;
x = 10; // TypeError: Assignment to constant variable

const y = [];
const y = [1, 2]; // SyntaxError: Identifier 'y' has already been declared

This design ensures the stability of constant identifiers, allowing illegal modification attempts to be detected at compile time.

Reference Types and Memory Models

The key to understanding const behavior lies in distinguishing between value types (primitives) and reference types in JavaScript. When declaring a primitive constant:

const num = 42;
const str = "hello";

The variables num and str directly store the actual values. Any modification would create a new value, and const prevents such reassignment.

However, when declaring reference type constants:

const arr = [];
const obj = {};

The variables arr and obj store references (pointers) to array or object instances in heap memory. const guarantees that this reference remains unchanged, not that the contents of the referenced object are immutable. Therefore, the following operations are valid:

const arr = [];
arr.push(1); // Modifying array contents
arr[0] = 2;  // Modifying array elements

const obj = {};
obj.name = "John"; // Adding a property
obj.name = "Jane"; // Modifying property value

These operations do not change the reference value stored in arr or obj; they only modify the state of the target object through this reference.

Analysis of Practical Code Examples

Consider the following typical scenarios:

// Example 1: Array operations
const numbers = [1, 2, 3];
numbers.push(4);        // Allowed: modifying array contents
console.log(numbers);   // Output: [1, 2, 3, 4]

// numbers = [5, 6];    // Error: attempted reassignment

// Example 2: Object operations
const person = { name: "Alice" };
person.age = 30;        // Allowed: adding new property
person.name = "Bob";    // Allowed: modifying existing property
console.log(person);    // Output: { name: "Bob", age: 30 }

// person = { name: "Charlie" }; // Error: attempted reassignment

This design has significant value in practical development. For instance, in frameworks like React or Vue, component state might be declared as constants, but the contents of state objects need to be dynamically updated based on user interactions. If const completely prohibited object modification, this pattern would be impossible to implement.

Achieving True Immutability

While const does not guarantee object content immutability, the ES5 Object.freeze() method provides this capability:

const frozenArray = Object.freeze([1, 2, 3]);
frozenArray.push(4); // TypeError: Cannot add property 3, object is not extensible
frozenArray[0] = 0;  // Silently fails (throws error in strict mode)
console.log(frozenArray); // Output: [1, 2, 3]

const frozenObject = Object.freeze({ x: 1, y: 2 });
frozenObject.z = 3; // TypeError: Cannot add property z, object is not extensible
frozenObject.x = 10; // Silently fails (throws error in strict mode)

It's important to note that Object.freeze() performs shallow freezing. If an object contains nested objects, these nested objects can still be modified:

const obj = Object.freeze({ data: { value: 1 } });
obj.data.value = 2; // Allowed: modifying nested object
console.log(obj.data.value); // Output: 2

To achieve deep immutability, recursive calls to Object.freeze() or specialized libraries like Immutable.js are required.

Design Philosophy and Best Practices

This design of const in JavaScript reflects the language's pragmatic philosophy. It balances the need for immutability with development flexibility:

  1. Identifier Stability: const ensures variable names always point to the same object, improving code readability and maintainability.
  2. Object Mutability: Allowing object content modification supports object-oriented programming paradigms, making state management more natural.
  3. Explicit Intent: When developers use const, they explicitly indicate that "this variable reference should not change," while object content changes are controlled through other mechanisms.

In practical development, it is recommended to:

Conclusion

The behavior of the const keyword in JavaScript is not a flaw but a carefully designed language feature. By guaranteeing immutable binding rather than immutable object contents, it provides immutability assurances while maintaining language flexibility. Understanding the memory model of reference types is key to mastering this feature. For scenarios requiring complete immutability, Object.freeze() offers a complementary solution. This design enables JavaScript to support both functional and object-oriented programming paradigms, adapting to the diverse requirements of modern web development.

Copyright Notice: All rights in this article are reserved by the operators of DevGex. Reasonable sharing and citation are welcome; any reproduction, excerpting, or re-publication without prior permission is prohibited.