JavaScriptのconstで配列が変更できる理由|pushは通って、代入だけがエラーになる
constで宣言した配列にpushしても、エラーは出ない。
constが禁止しているのは「再代入」で、中身の書き換えではないからだ。MDNの説明を読んで、Object.freeze()の浅さまで整理した。
JavaScriptで const と書いた配列に push すると、エラーは出ずに要素が増える。
同じ変数に新しい配列を代入すると、今度は TypeError になる。
MDNの説明では、const が禁止しているのは「変数への再代入」で、「中身の変更」ではない。
この記事は、MDNの const と Object.freeze() のページを読んで、僕が理解した範囲で整理したものだ。
自分のサイトで起きた不具合の話ではないので、体験談は入れていない。
仕様と書き方の話に絞った。
push は通って、代入はエラーになる
MDNに載っている例を、そのまま並べるとこうなる。
const MY_ARRAY = [];
MY_ARRAY.push("A"); // ["A"] … エラーにならない
MY_ARRAY = ["B"]; // TypeError: Assignment to constant variableオブジェクトでも同じだ。
const MY_OBJECT = { key: "value" };
MY_OBJECT.key = "otherValue"; // エラーにならない
MY_OBJECT = { OTHER_KEY: "value" }; // TypeError「定数」という言葉から、中身ごと固まると思うと、ここで混乱する。
push と key の書き換えは、どちらも同じ配列・同じオブジェクトの中を触っているだけだ。
エラーになるのは、= で別の配列や別のオブジェクトを結び付け直そうとしたときだけになる。
const が固定しているのは「どれを指すか」
MDNは、const 宣言は値への「不変の参照」を作る、と書いている。
値が不変になるという意味ではなく、変数の識別子に再代入できないという意味だ、と続く。
言い換えると「アイデンティティが一定の変数」で、「値が一定の変数」ではない。
僕は、名札をイメージすると分かりやすいと思っている。
const は、名札を一つの箱に縫い付ける。
名札を別の箱へ付け替えることはできないが、箱の中に物を入れたり出したりするのは自由だ。
数値や文字列(プリミティブ)は、そもそも中身を書き換える手段がない。
だから const にすると、結果として値も変わらなくなる。
MDNが「特にプリミティブの場合は本当に変更不可能」と書いているのはこのためだ。
中身まで固めたいときは Object.freeze()
オブジェクトや配列を書き換えられないようにしたいときは、Object.freeze() を使う、とMDNにある。
凍結すると、プロパティの追加・削除・値の変更ができなくなる。
配列も凍結でき、要素の変更・追加・削除ができなくなる。
const SETTINGS = Object.freeze({ tax: 10 });
SETTINGS.tax = 8;
console.log(SETTINGS.tax); // 10注意したいのは、書き換えに失敗してもエラーが出ない場合があることだ。
MDNでは、失敗は「静かに」起きるか TypeError になり、厳格モードではエラーになることが多いと説明している。
エラーが出ないまま値だけ元のまま、という形になりうる。
freeze は一段目しか凍らない
もう一つの落とし穴は、凍結が浅いことだ。
Object.freeze() が効くのは、そのオブジェクトの直属のプロパティだけになる。
中に入っているオブジェクトは凍結されない。
const obj1 = { internal: {} };
Object.freeze(obj1);
obj1.internal.a = "value";
console.log(obj1.internal.a); // "value"入れ子の中まで固めたい場合、MDNは中のオブジェクトを順にたどって凍結する「深い凍結」の書き方を紹介している。
一段しか効かないという点は、スプレッド構文のコピーが一段しか効かない話と同じ形をしている。
僕の使い分け
MDNを含む多くのスタイルガイドは、再代入しない変数には let ではなく const を使うよう勧めている。
僕もそれに従って、基本は const で書き、あとで別の値を入れ直すものだけ let にするのがいいと思う。
ただ、const と書いてあっても「中身は変わるかもしれない」と読むのが正しい。
設定値の一覧のように、途中で書き換えられたら困るものだけ Object.freeze() をかける。
入れ子になっているなら、一段目だけでは足りないことを覚えておく。
このくらいの使い分けで、僕は十分だと考えている。
比較の落とし穴は==と===の違いの記事にまとめた。
出典
ほかにも書いています
受託・note・スタンプこういう仕組みを作る相談を受けています
毎日の更新・データ集め・集計の自動化/3万円〜収益がいくらだったか、どの施策が外れたか
note / 個人の視点で掘り下げていますスタンプラリーに挑戦する
行った場所は、現地チェックインで記録に残せます※受託の相談とnoteは、運営者が個人で受けている・書いているものです。
