#概要
・constは再代入できないが参照型は変更が可能
・参照型はObject.freezeメソッドで不変な値にできる
#constは変更できる
まず初めに、constで定数を定義すると再代入・再宣言ができません。
そのため、次のコードでは「TypeError: Assignment to constant variable.」というエラーが発生します。
const teisu = "定数です。"
teisu = "変更しました" //エラー発生
console.log(teisu)
しかし、constは再代入することができないだけでプロパティの一部を変更することができます。
const teisu = ["定数です。"]
teisu[0] = "変更しました"
console.log(teisu[0]) //「変更しました」が出力
#なぜconstを変更できたのか?
なぜStringは変更できなかったのに、配列の要素は変更できたのでしょうか?
これには2種類のデータ型が関連しています。
・基本型(プリミティブ型)
・参照型
この2つの型は値の保存方法が異なります。
基本型は値を直接格納するのに対し、参照型は定数値の格納先のアドレスを格納します。
・基本型 ⇒ 値を格納
・参照型 ⇒ アドレスを格納
定数は値を変更することができません。そのため、基本型は変更すればエラーが発生しました。
しかし、参照型はアドレスを変更せず、アドレスが指している値を変更しているため変更が可能なのです。
#参照型の変更を禁止するには?
アプリを作ると定数ファイルを用意して、共通の値を管理したいことが出てきます。その場合、参照型の値も保持したい場面が出てくるでしょう。
しかし、参照型はconstで定義しても変更することができるのでした。それでは本来の目的である不変な値を維持することができません。
どうすればいいのでしょうか?
この問題を解決するのが、Object.freezeメソッドです。
このメソッドを使って定義すれば、プロパティの変更・追加・削除をしても内容が反映されません。
次のソースでは配列で定義した値をソース上は変更していますが、値を出力すると変更されていないことがわかります。
const teisu = Object.freeze(["定数です。"])
teisu[0] = "変更しました"
console.log(teisu[0]) //「定数です。」が出力
また、Strictモードにすると TypeError エラーが発生させることができます。
これにより、定数を書き換える処理をテストの段階で取り除けるため、無駄な記述を減らせて可読性が上がります。
"use strict"
const teisu = Object.freeze(["定数です。"])
teisu[0] = "変更しました" //エラー発生
console.log(teisu[0])
ただし、Object.freezeメソッドを使うには注意点が必要です。このメソッドはobjectの直下のプロパティーのみに適応されます。
そのため、入れ子になっているプロパティーの値は変更することができます。
const teisu = Object.freeze(["定数です。", ["定数の中の値"]])
teisu[1][0] = "変更しました"
console.log(teisu[1][0]) //"変更しました"
この問題を解決するために公式ではdeepFreezeメソッドを紹介しています。
このメソッドを使えば、入れ子になっている値も変更することができません。
function deepFreeze(object) {
// オブジェクトで定義されたプロパティ名を取得
var propNames = Object.getOwnPropertyNames(object);
// 自分自身を凍結する前にプロパティを凍結
for (let name of propNames) {
let value = object[name];
//「値がある かつ オブジェクト型」の場合、freezeを適用
object[name] = value && typeof value === "object" ?
deepFreeze(value) : value;
}
return Object.freeze(object);
}
var obj2 = {
internal: {
a: null
}
};
deepFreeze(obj2);
obj2.internal.a = 'anotherValue'; // 非 strict モードでは暗黙に失敗
obj2.internal.a; // null
#参考
MDN web docs