オブジェクトの拡張や値の変更を抑制する
ここでは JavaScript のObject オブジェクト の拡張や値の変更を禁止する方法を説明します。
プロパティの操作を禁止する関数の種類と関係
プロパティの操作を禁止するために、Object の関数として次の三つがあります。
- Object.preventExtensions() オブジェクトの拡張を禁止
- Object.seal() オブジェクトの拡張、削除を禁止
- Object.freeze() オブジェクトの拡張、削除および値の変更を禁止
オブジェクトのプロパティ属性を指定して設定することでも、同様の効果を得られます。詳しくは「オブジェクトのプロパティ属性」をみてください。
この三つの関数は、Object.preventExtensions()、Object.seal()、Object.freeze() の順番で操作の禁止項目が多くなります。Object.seal()はObject.preventExtensions()を含み、 Object.freeze()は残りの二つを含みます。
オブジェクトに適用した関数と効果の関係は次の表のようになります。
関数名 | プロパティ追加 | プロパティ削除 | 値の変更 |
---|---|---|---|
Object.preventExtensions() | 不可 | 可 | 可 |
Object.seal() | 不可 | 不可 | 可 |
Object.freeze() | 不可 | 不可 | 不可 |
こうした動作を踏まえて、以下をお読みいただくとわかりやすいと思います。
オブジェクトの拡張を禁止Object.preventExtensions()
オブジェクトの拡張というのは、プロパティの追加のことです。オブジェクトに新しくプロパティを追加することを禁止するには、Object.preventExtensions(o) メソッドを使います。
'use strict';
const a = {
name: 'Mike',
age: 25
};
Object.preventExtensions(a);
console.log(a); // {name: "Mike", age: 25}
a.age = 30;
a.city = 'Los Angeles'; // エラー (strict mode), 無視 (non-strict)
実行結果は次の通りです。age の値は上書きされていますが city という属性は追加されていません。
これは strict モードで実行しているのでエラーが発生していますが、strict モードでない場合は単にプロパティの追加を試みたことは無視されます。
オブジェクトの拡張 (すなわち、プロパティの追加) が禁止されているか確認するには、Objectd.isExtensible(o) 関数が使えます。 これにオブジェクトを渡すと、拡張が禁止されている状態では false が返ります。
const a = {
name: 'Mike',
age: 25
};
console.log(Object.isExtensible(a)); // true
Object.preventExtensions(a);
console.log(Object.isExtensible(a)); // false
Object.preventExtensions()を呼ぶことで、プロパティの追加は禁止されますが、プロパティの削除は許可されたままです。
また、Object.preventExtensions()は属性の変更を行った後のオブジェクトを返すので、 次のようにオブジェクトを作成することもできます。
'use strict';
const a = Object.preventExtensions({
name: 'Mike',
age: 25
});
console.log(Object.isExtensible(a)); // false
console.log(a); // {name: "Mike", age: 25}
delete a.age;
console.log(a); // {name: "Mike"}
オブジェクトの拡張を禁止Object.seal()
オブジェクトに新しくプロパティを追加することを禁止し、さらにプロパティの削除も禁止するには、Object.seal(o) メソッドを使います。シール (seal) は「封をする」というような意味です。
const a = {
name: 'Mike',
age: 25
};
Object.seal(a);
console.log(a);
a.age = 30;
a.city = 'Los Angeles';
console.log(a);
実行結果は次の通りです。age の値は上書きされていますが city という属性は追加されていません。
ちなみに、このとき、オブジェクトに対する更新操作は単に無視されています。 もし、拡張を試みたときに、直ちにエラーとして検出したい場合は、strict モードで実行すれば直ちにエラーになります。
'use strict';
const a = {
name: 'Mike',
age: 25
};
Object.seal(a);
...
オブジェクトがシールされているか確認するには、Objectd.isSealed(o) が使えます。 これにオブジェクトを渡すと、シールされている状態なら true が返ります。
オブジェクトの拡張および値の変更を禁止Object.freeze()
オブジェクトへのプロパティの追加と削除に加えて、値の変更も禁止するには、Object.freeze(o) メソッドを使います。
一度フリーズしたらその状態を戻すことはできません。もし変更が必要ならば、オブジェクトの内容をコピーした別のオブジェクトを作る必要があります。
const a = {
name: 'Mike',
age: 25
};
console.log( 'Frozen? ' + Object.isFrozen(a));
Object.freeze(a);
console.log( 'Frozen? ' + Object.isFrozen(a));
console.log(a);
a.age = 30;
console.log(a);
実行結果は次の通りです。age の値の変更操作は無視されています。
属性の変更操作を試みたときに、直ちにエラーとして検出したい場合は、seal() のときと同様、 strict モードで実行すれば直ちにエラーになります。
オブジェクトがフリーズされているか確認するには、Objectd.isFrozen(o) が使えます。 これにオブジェクトを渡すと、フリーズされている状態なら true が返ります。
フリーズをしたからといって、そのオブジェクトのデータが一切変更できないわけではないことに注意しましょう。
Object.freeze()は浅い (shallow) フリーズを行います。プロパティがオブジェクトへの参照を持っていたら、 参照自体は変更できませんが、子のオブジェクトが保持するデータまではフリーズされません。
const a = Object.freeze({
name: 'Mike',
address: {
city: 'Yokohama'
}
});
a.name = 'Ryan'; // Ignored (strict モードの時はエラー)
console.log(a.name); // Mike (変更されていない)
console.log(a.address); // {city: "Yokohama"}
a.address.city = 'Sapporo';
console.log(a.name); // Mike
console.log(a.address); // {city: "Sapporo"} (更新された)
この例のようにこのオブジェクトが参照するデータは書き換え可能です。