萬萬沒想到!ES6的const並非一定為常量

程式碼灣發表於2018-07-25

對於ES6的const變數,大家一直存在誤會,這篇部落格將試著解開真相。

const的本質

const定義的變數並非常量,並非不可變。使用const定義的物件或者陣列,其實是可變的。下面的程式碼並不會報錯:

const foo = {};
foo.name = "Fundebug";
console.log(foo.name); // 列印"Fundebug"

為什麼會這樣?下面引用大神阮一峰的ECMAScript 6 入門

const實際上保證的,並不是變數的值不得改動,而是變數指向的那個記憶體地址所儲存的資料不得改動。對於簡單型別的資料(數值、字串、布林值),值就儲存在變數指向的那個記憶體地址,因此等同於常量。但對於複合型別的資料(主要是物件和陣列),變數指向的記憶體地址,儲存的只是一個指向實際資料的指標,const只能保證這個指標是固定的(即總是指向另一個固定的地址),至於它指向的資料結構是不是可變的,就完全不能控制了。因此,將一個物件宣告為常量必須非常小心。

因此,當我們使用賦值運算子, 一元運算子以及字尾運算子對const變數進行修改時,會出現“TypeError:Assignment to constant variable”報錯:

const foo = 27;

// 下面任意一個表示式都會報錯:TypeError: Assignment to constant variable.
foo = 42; 
foo *= 42;
foo /= 42;
foo %= 42;
foo += 42;
foo -= 42;
foo <<= 0b101010;
foo >>= 0b101010;
foo >>>= 0b101010;
foo &= 0b101010;
foo ^= 0b101010;
foo |= 0b101010;


--foo;
++foo;


foo--;
foo++;

如何定義常量?

對於數值、字串和布林值變數,使用const定義的話是不可變的:

const name = "Fundebug";
name = "雲麒"; // 報錯:TypeError: Assignment to constant variable.

使用Object.freeze()可以讓物件不可變,這個API從ES5開始就有了。

const foo = Object.freeze(
{
    'name': "Fundebug"
});

foo.name = "雲麒"; // 嚴格模式("use strict")下報錯:Uncaught TypeError: Cannot assign to read only property 'bar' of object '#<Object>'

console.log(foo.name); // 非嚴格模式下列印"Fundebug"

注意,Object.freeze()是有侷限性的,物件中巢狀物件仍然可以被修改。MDN提供了一下deepFreeze示例程式碼,是利用Object.freeze()實現的,可以讓巢狀物件也不可變。

另外,Object.freeze()僅對鍵值對有效。對於Date, Map和Set,目前還沒有相應方法。因此有人提議在ECMAScript標準中新增不可變的Map和Set等資料型別。

const和let如何選擇?

const和let的唯一區別在於,const可以讓數值、字串和布林變數不可變。

以上所說的內容都是事實,下面我想表達一下自己的觀點。

const可以提高程式碼的可讀性,因為const定義的數值、字串和布林變數是不可變的,而const定義的物件始終指向同一個物件。而使用let時,不能保證這些。因此,對於let和const,我們應該這樣選擇:

  • 預設使用const
  • 僅當變數需要修改時使用let
  • 不要使用var

你是否同意我的觀點呢?歡迎大家交流討論~

相關文章