お知らせ:2021/2/18 ツールサイト(affi-sapo-sv.com)から、開発ノートを独立させました。
【JavaScript】 オブジェクトのプロパティ追加を禁止する方法
更新日:2021/01/25
ツイート自作のオブジェクトを共有ライブラリとして外部に公開するときなど、他者にオブジェクトを変更してほしくないときもあります。
タイプミスが頻繁で、予定にない新規プロパティが増えていて困るという人(僕)もいます。
そんなとき、Object.freeze()またはObject.seal()またはObject.preventExtensions()を使用します。
オブジェクトにプロパティを追加させたくないケース
既存プロパティの変更を防ぐなら、Object.defineProperty()というメソッドを使用します。しかしプロパティの数が増えてくると、一つ一つ属性を与えていくのはめんどうです。普通に定義しておいて、後で一括で変更できると嬉しいですね。
それと、Object.defineProperty()を使っても、オブジェクトに新規でプロパティを追加させることを制限できません。
そこでObject.freeze()またはObject.seal()またはObject.preventExtensions()を使用すると、一括で属性を変更したり、オブジェクトのプロパティ追加を禁止したりできます。
メソッド | プロパティ追加 | writable属性 | configurable属性 | 目的 |
---|---|---|---|---|
Object.freeze() | 禁止 | false | false | 完全凍結:
値の変更もできなくする |
Object.seal() | 禁止 | 変更なし | false | プロパティ値の変更はできる |
Object.preventExtensions() | 禁止 | 変更なし | 変更なし | プロパティ追加禁止のみ |
上の表を見てわかるように、この三つのメソッドはフリーな状態から禁止する方向に属性を変更します。
一度これらのメソッドを呼び出した後、解除する方法はありません。
一見不便なように感じますが、これらのメソッドはセキュリティ強化という目的で呼び出されれます。
解除する方法があるとその目的を達成することができないので、仕方がないのです。
Object.freeze()
Object.freeze()は、既存プロパティのwritableとconfigurable属性をfalseに設定します。
さらに、オブジェクトのプロパティ追加を不可にするフラグをセットします。
これによりオブジェクトの新規プロパティの追加、および既存プロパティの値と属性の変更が禁止されます。
Object.freeze()は、オブジェクトの改変をプロパティが持っている値を含めて禁止したいときに使用します。
構文
Object.freeze(obj)
■引数
obj: 凍結させるオブジェクト
■戻り値
引数で指定したオブジェクト
■使用例
"use strict"; const obj ={ prop1:100, prop2:200, prop3:{} }; // 現在の属性を列挙 Object.getOwnPropertyNames(obj).forEach(e=>{ const od = Object.getOwnPropertyDescriptor(obj,e); console.log( e ,od ); // prop1{ value: 100, writable: true, enumerable: true, configurable: true } // prop2{ value: 200, writable: true, enumerable: true, configurable: true } // prop3{ value: {}, writable: true, enumerable: true, configurable: true } }); // オブジェクトをフリーズ Object.freeze(obj); // フリーズ後の属性を列挙 → writable・configurable属性がfalseに変更されている Object.getOwnPropertyNames(obj).forEach(e=>{ const od = Object.getOwnPropertyDescriptor(obj,e); console.log( e ,od ); // prop1{ value: 100, writable: false, enumerable: true, configurable: false } // prop2{ value: 200, writable: false, enumerable: true, configurable: false } // prop3{ value: {}, writable: false, enumerable: true, configurable: false } }); obj.prop1 = 500; //TypeError: "prop1" is read-only
上の例では、writable属性をfalseに変更したことにより上書きが禁止されたプロパティに値を代入し、その結果エラーがスローされています。
これは、strictモードでの動作です。
非strictモードはエラーがスローされず、そのまま続行されます。
Object.seal()
Object.seal()は、既存プロパティのconfigurable属性をfalseに設定します。
さらに、オブジェクトのプロパティ追加を不可にするフラグをセットします。
これによりオブジェクトの新規プロパティの追加、および既存プロパティの属性変更が禁止されます。
Object.seal()は、オブジェクトの構造改変を禁止したいときに使用します。
構文
Object.seal(obj)
■引数
obj: シールさせるオブジェクト
■戻り値
引数で指定したオブジェクト
■使用例
"use strict"; const obj ={ prop1:100, prop2:200, prop3:{} }; // 現在の属性を列挙 Object.getOwnPropertyNames(obj).forEach(e=>{ const od = Object.getOwnPropertyDescriptor(obj,e); console.log( e ,od ); // prop1{ value: 100, writable: true, enumerable: true, configurable: true } // prop2{ value: 200, writable: true, enumerable: true, configurable: true } // prop3{ value: {}, writable: true, enumerable: true, configurable: true } }); // オブジェクトをシール Object.seal(obj); // シール後の属性を列挙 → configurable属性がfalseに変更されている Object.getOwnPropertyNames(obj).forEach(e=>{ const od = Object.getOwnPropertyDescriptor(obj,e); console.log( e ,od ); // prop1{ value: 100, writable: true, enumerable: true, configurable: false } // prop2{ value: 200, writable: true, enumerable: true, configurable: false } // prop3{ value: {}, writable: true, enumerable: true, configurable: false } }); obj.prop1 = 500; // writableがtrueのままなので、値を変更可能
Object.seal()を実行すると、obj直下への新規プロパティ追加と既存プロパティの削除がおこなえなくなります。
オブジェクトとその直下のプロパティ
obj{ プロパティ プロパティ プロパティ }
しかしプロパティ値の変更が禁止されていないので、オブジェクトを代入することで、オブジェクトの入れ子構造などの改変をおこなうことはできます。
入れ子構造などの改変
obj{ プロパティ プロパティ プロパティ: ←代入 obj{ プロパティ プロパティ } }
Object.seal()実行前にObject.defineProperty()でwritable属性をfalseにセットしておくことで、一部のプロパティのみ上書き禁止することができます。
"use strict"; const obj ={ prop1:100, // prop2:200, prop3:{} }; Object.defineProperty(obj,"prop2",{ value:200, writable:false , // 規定値 }); Object.getOwnPropertyNames(obj).forEach(e=>{ const od = Object.getOwnPropertyDescriptor(obj,e); console.log( e ,od ); // prop1{ value: 100, writable: true, enumerable: true, configurable: true } // prop3{ value: {}, writable: true, enumerable: true, configurable: true } // prop2{ value: 200, writable: false, enumerable: true, configurable: true } }); Object.seal(obj); Object.getOwnPropertyNames(obj).forEach(e=>{ const od = Object.getOwnPropertyDescriptor(obj,e); console.log( e ,od ); // prop1{ value: 100, writable: true, enumerable: true, configurable: false } // prop3{ value: {}, writable: true, enumerable: true, configurable: false } // prop2{ value: 200, writable: false, enumerable: true, configurable: false } }); obj.prop2 = 500; //TypeError: "prop2" is read-only
Object.preventExtensions()
Object.preventExtensions()は、オブジェクトのプロパティ追加を不可にするフラグをセットします。
これにより、以降のプロパティ追加ができなくなります。
Object.preventExtensions()は、既存プロパティの属性はそのままで、オブジェクトのプロパティ追加を禁止したいとき使用します。
構文
Object.preventExtensions(obj)
■引数
obj: プロパティ追加を禁止するオブジェクト
"use strict"; const obj ={ prop1:100, prop2:200, prop3:{} }; // 現在の属性を列挙 Object.getOwnPropertyNames(obj).forEach(e=>{ const od = Object.getOwnPropertyDescriptor(obj,e); console.log( e ,od ); // prop1{ value: 100, writable: true, enumerable: true, configurable: true } // prop2{ value: 200, writable: true, enumerable: true, configurable: true } // prop3{ value: {}, writable: true, enumerable: true, configurable: true } }); // オブジェクトのプロパティ追加を禁止 Object.preventExtensions(obj); // 禁止後の属性を列挙 → 属性の変更なし Object.getOwnPropertyNames(obj).forEach(e=>{ const od = Object.getOwnPropertyDescriptor(obj,e); console.log( e ,od ); // prop1{ value: 100, writable: true, enumerable: true, configurable: true } // prop2{ value: 200, writable: true, enumerable: true, configurable: true } // prop3{ value: {}, writable: true, enumerable: true, configurable: true } }); obj.newProp = 100; // Uncaught TypeError: can't define property "newProp": Object is not extensible
属性変更の範囲
既存プロパティの属性に左右されない
Object.defineProperty()を使用すると、for-in文やObject.keys()での列挙許可(enumerable属性)や属性の変更許可(configurable属性)を設定できます。
Object.freeze()とObject.seal()はこれらの属性に左右されず、オブジェクトが直接所持しているプロパティ全ての属性を変更します。
入れ子構造は対象外
Object.freeze()とObject.seal()の対象となるのは、オブジェクトの直下のプロパティのみです。
オブジェクトのプロパティ中にオブジェクトが存在する入れ子状態のとき、入れ子となっているオブジェクトの属性は変更されません。
変更したいときは、個別にメソッドを実行する必要があります。
const obj ={ prop1:100, prop3:{ prop31:100, prop32:{ prop321:100 } } }; Object.freeze(obj); Object.freeze(obj.prop3); Object.freeze(obj.prop3.prop32);
配列のフリーズ
配列はArrayオブジェクトのため、Object.freeze()とObject.seal()を使用できます。
const array =[1,2,3]; Object.freeze(array); Object.getOwnPropertyNames(array).forEach(e=>{ const od = Object.getOwnPropertyDescriptor(array,e); console.log( e ,od ); // 0 { value: 1, writable: false, enumerable: true, configurable: false } // 1 { value: 2, writable: false, enumerable: true, configurable: false } // 2 { value: 3, writable: false, enumerable: true, configurable: false } // length { value: 3, writable: false, enumerable: false, configurable: false } }); array[0]=100; // TypeError: 0 is read-only
ただし多次元配列は、オブジェクトの入れ子になります。
そのため配列全体を凍結するには、入れ子となっているオブジェクトにもメソッドを適用する必要があります。
const array =[ [10,11,12],[20,21,22],[30,31,32] ]; Object.freeze(array); array[2][2]=100; // エラーにならない for( let i = 0; i < array.length ; i++ ){ Object.freeze(array[i]); } array[2][2]=100; // TypeError: 2 is read-only
記事の内容について

説明するのって難しいですね。
「なんか言ってることおかしくない?」
たぶん、こんなご意見あると思います。
裏付けを取りながら記事を作成していますが、僕の勘違いだったり、そもそも情報源の内容が間違えていたりで、正確でないことが多いと思います。
そんなときは、ご意見もらえたら嬉しいです。
ご意見はこちら。
https://note.affi-sapo-sv.com/info.php
【お願い】

■このページのURL
■このページのタイトル
■リンクタグ