跳至主要内容
The “const” Deception

from Josh W Comeau

Beginner

/

JavaScript / TypeScript

const 常常會讓 JavaScript 的初學者感到困惑。例如為什麼用 const 初始化的 objectarray 可以被修改,但是利用 const 初始化的 stringnumber 卻不能被修改呢?

這是因為通常我們修改 objectarray 的動作 (push, splice) 稱為 mutation,並不會修改到物件的記憶體位置,所以與 const 並無關係。而修改 stringnumber 的動作稱為 reassignment,是直接修改物件的記憶體位置,所以就與 constlet 有關係了。

Key Takeaways
  • 使用 const 創建的物件可以被修改 (mutate),但是不能被重新指派 (reassign)。
  • mutate 指的是修改物件的內容,但是不會改變物件的記憶體位置。例如使用push 修改 array 的內容,或是修改 object 的內容: obj.name = 'John'
  • reassign 指的是修改物件的記憶體位置。例如使用 = 重新指派stringnumber 的值。只有使用 let 宣告的變數才能被重新指派。
  • String, Number, Boolean 等都是屬於不可修改 (immutable) 的物件,所以無法被 mutate。我們只能透過 reassign 的方式來改變它們的值。例如 let name = 'John'; name = 'Mary'; 或是 let age = 20; age += 1;

Variable Reassignment

在 JavaScript 有兩種宣告變數 (variable) 的方式:constlet。如果你用 const 宣告變數,就代表這個變數的值不能被重新指派 (reassign)。如果你用 let 宣告變數,就代表這個變數的值可以被重新指派。這個規則對於 primitive types (string, number, boolean, null, undefined) 和 object (object, array) 都是一樣的。

Primitive Types

❌ 不行重新指派 const 宣告的變數 (primitive types)
const name = "John";
name = "Mary"; // TypeError: Assignment to constant variable.
✅ 可以重新指派 let 宣告的變數 (primitive types)
let name = "John";
name = "Mary";
console.log(name); // Mary

Object Types

❌ 不行重新指派 const 宣告的變數 (object types)
const person = { name: "John" };
person = { name: "Mary" }; // TypeError: Assignment to constant variable.
✅ 可以重新指派 let 宣告的變數 (object types)
let person = { name: "John" };
person = { name: "Mary" };
console.log(person); // { name: 'Mary' }

Variable Mutation

Object (object, array) 在 const 的情況下,雖然不能重新指派,但是可以修改 (mutate)。這是因為 const 只是保證變數的記憶體位置不會被改變,但是並不保證在同樣的記憶體下,變數的內容不會被改變。

✅ 可以修改 const 宣告的變數 (object )
const person = { name: "John" };
person.name = "Mary";
console.log(person); // { name: 'Mary' }
✅ 可以修改 const 宣告的變數 (array)
const numbers = [1, 2, 3];
numbers.push(4);
console.log(numbers); // [ 1, 2, 3, 4 ]
提示

如果想要避免變數被修改,可以使用 Object.freeze 來凍結物件。

const person = Object.freeze({ name: "John" });
person.name = "Mary";
console.log(person); // { name: 'John' }

但要注意的是,Object.freeze 只會凍結第一層的物件,如果物件內還有其他物件,那麼這些物件還是可以被修改。如果要凍結所有的層級,可以使用 deep-freeze 的方式凍結所有的層級。

const person = Object.freeze({ name: "John", address: { city: "Taipei" } });
person.address.city = "New Taipei";
console.log(person); // { name: 'John', address: { city: 'New Taipei' } }

另外,我們也可以透過 TypeScript 的 as const 在編譯前就確保物件不會被修改。

const person = { name: "John" } as const;
person.name = "Mary"; // error: Cannot assign to 'name' because it is a read-only property.

Primitive Types

Primitives (string, number, boolean, null, undefined) 指的是不可修改 (immutable) 的物件。這代表我們無法透過 mutate 的方式來改變它們的值,我們只能透過 reassign 的方式重新分配一個記憶體位置來改變它們的值。

let name = "John";
name = "Mary";
console.log(name); // Mary

let age = 20;
age += 1;
console.log(age); // 21