4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

constで定数管理するのはヤバい!?値の変更を禁止するたった1つの方法!

Posted at

#概要
・constは再代入できないが参照型は変更が可能
・参照型はObject.freezeメソッドで不変な値にできる

#constは変更できる
まず初めに、constで定数を定義すると再代入・再宣言ができません
そのため、次のコードでは「TypeError: Assignment to constant variable.」というエラーが発生します。

定数が変更できない例.js
const teisu = "定数です。"
teisu = "変更しました"    //エラー発生
console.log(teisu)

しかし、constは再代入することができないだけでプロパティの一部を変更することができます

定数が変更できる例.js
const teisu = ["定数です。"]
teisu[0] = "変更しました"
console.log(teisu[0])   //「変更しました」が出力

#なぜconstを変更できたのか?
なぜStringは変更できなかったのに、配列の要素は変更できたのでしょうか?

これには2種類のデータ型が関連しています。
・基本型(プリミティブ型)
・参照型

テーブル.png

この2つの型は値の保存方法が異なります。
基本型は値を直接格納するのに対し、参照型は定数値の格納先のアドレスを格納します。
・基本型 ⇒ 値を格納
・参照型 ⇒ アドレスを格納

参照型と基本型.png

定数は値を変更することができません。そのため、基本型は変更すればエラーが発生しました。
しかし、参照型はアドレスを変更せず、アドレスが指している値を変更しているため変更が可能なのです。

参照先の変更.png

#参照型の変更を禁止するには?
アプリを作ると定数ファイルを用意して、共通の値を管理したいことが出てきます。その場合、参照型の値も保持したい場面が出てくるでしょう。

しかし、参照型はconstで定義しても変更することができるのでした。それでは本来の目的である不変な値を維持することができません。
どうすればいいのでしょうか?

この問題を解決するのが、Object.freezeメソッドです。
このメソッドを使って定義すれば、プロパティの変更・追加・削除をしても内容が反映されません。

次のソースでは配列で定義した値をソース上は変更していますが、値を出力すると変更されていないことがわかります。

freezeメソッドで変更を禁止.js
const teisu = Object.freeze(["定数です。"])
teisu[0] = "変更しました"
console.log(teisu[0])   //「定数です。」が出力

また、Strictモードにすると TypeError エラーが発生させることができます。
これにより、定数を書き換える処理をテストの段階で取り除けるため、無駄な記述を減らせて可読性が上がります。

strictモードのエラー.js
"use strict"

const teisu = Object.freeze(["定数です。"])
teisu[0] = "変更しました"   //エラー発生
console.log(teisu[0])  

ただし、Object.freezeメソッドを使うには注意点が必要です。このメソッドはobjectの直下のプロパティーのみに適応されます。
そのため、入れ子になっているプロパティーの値は変更することができます。

入れ子のプロパティは変更可能.js
const teisu = Object.freeze(["定数です。", ["定数の中の値"]])
teisu[1][0] = "変更しました"   
console.log(teisu[1][0])   //"変更しました"

この問題を解決するために公式ではdeepFreezeメソッドを紹介しています。
このメソッドを使えば、入れ子になっている値も変更することができません。

入れ子のプロパティも変更できない.js
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

4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?