オブジェクトも内部属性を持っている。
その一つが、[[Extensible]]
。
これはオブジェクトが拡張可能かどうかを示し、これを使うことで、プロパティの追加や変更を制御できる。
Extensible
[[Extensible]]
はデフォルトではtrue
になっている。
この状態だと自由にプロパティを追加できるが、[[Extensible]]
がfalse
になっていると、プロパティを追加することは出来ない。
Object.preventExtensions()
メソッドの引数にオブジェクトを渡すことで、そのオブジェクトの[[Extensible]]
をfalse
に出来る。
[[Extensible]]
の状態は、Object.isExtensible()
メソッドを使う。これも、引数にオブジェクトを渡す。
var person = { name: 'Tom' }; console.log(Object.isExtensible(person)); // true Object.preventExtensions(person); console.log(Object.isExtensible(person)); // false // 何も行われない。strictモードだとエラーになる。 person.age = '30'; console.log(person.age); // undefined
シール
Object.seal()
メソッドの引数にオブジェクトを渡すと、そのオブジェクトをシールすることが出来る。
シールされたオブジェクトは、[[Extensible]]
がfalse
になり、さらに、既存のプロパティの[[Configurable]]
がfalse
になる。
オブジェクトがシールされているかどうかは、Object.isSealed()
にオブジェクトを渡すことで確認できる。
[[Configurable]]
などのプロパティの内部属性の挙動については、下記参照。
プロパティの内部属性
var person = { name: 'Tom' }; Object.seal(person); console.log(Object.isExtensible(person)); // false console.log(Object.isSealed(person)); // true // 何も行われない。strictモードだとエラーになる。 delete person.name; console.log(person.name); // Tom // 何も行われない。strictモードだとエラーになる。 person.age = '30'; console.log(person.age); // undefined
フリーズ
Object.freeze()
メソッドの引数にオブジェクトを渡すと、そのオブジェクトはフリーズされる。
フリーズされたオブジェクトは、[[Extensible]]
がfalse
になり、さらに、既存のプロパティの[[Configurable]]
がfalse
になる。そして、データプロパティについては、[[Writable]]
もfalse
になる。
オブジェクトがフリーズされているかどうかは、Object.isFrozen()
にオブジェクトを渡すことで確認できる。
var person = { name: 'Tom' }; Object.freeze(person); console.log(Object.isExtensible(person)); // false console.log(Object.isSealed(person)); // true console.log(Object.isFrozen(person)); // true var descriptor = Object.getOwnPropertyDescriptor(person, 'name'); console.log(descriptor); // Object {value: "Tom", writable: false, enumerable: true, configurable: false}
setter
が使えなくなる
オブジェクトをフリーズすると、setter
の内容によっては、それを使えなくなる。
var person = { _name: 'Tom', get name(){ return this._name; }, set name(value){ this._name = value; } }; Object.seal(person); person.name = 'Bob'; console.log(person.name); // Bob Object.freeze(person); person.name = 'Ichiro'; // これは動かない。strictモードだとエラーになる。 console.log(person.name); // Bob
上記の例ではsetter
は_name
の値を操作しているが、オブジェクトをフリーズすると全てのプロパティは書き込み禁止になるため、これは動かなくなる。
そして、setter
自身も[[Configurable]]
がfalse
になっているため、その内容を変更することも削除することも出来ず、このsetter
は二度と使えない。
変数への再代入は可能
オブジェクトの変更防止に関する設定は、あくまでもオブジェクトそのものに対するものであり、オブジェクトを格納している変数に対するものではない。
変数は何もロックされていないため、変数への値の代入は問題なく行われる。
var person = { _name: 'Tom' }; Object.freeze(person); person = { _name: 'Ichiro' }; console.log(person._name); // Ichiro
シールやフリーズの定義
シールの定義
オブジェクトの
[[Extensible]]
がfalse
であり、
全てのプロパティの
[[Configurable]]
がfalse
だが、
[[Writable]]
がtrue
のデータプロパティがある。
フリーズの定義
オブジェクトの
[[Extensible]]
がfalse
であり、
全てのプロパティの
[[Configurable]]
がfalse
かつ、
[[Writable]]
がtrue
のデータプロパティがない。
Object.isSealed()やObject.isFrozen()の判定
上記の定義を満たしていれば、Object.isSealed()
やObject.isFrozen()
はtrue
を返す。
そのため、Object.seal()
やObject.freeze()
を使わなくても、オブジェクトをシールしたり、フリーズさせたりすることは可能である。
var person = { name: 'Tom' }; Object.preventExtensions(person); console.log(Object.isExtensible(person)); // false console.log(Object.isSealed(person)); // false console.log(Object.isFrozen(person)); // false Object.defineProperty(person, 'name', { configurable: false }); console.log(Object.isSealed(person)); // true console.log(Object.isFrozen(person)); // false Object.defineProperty(person, 'name', { writable: false }); console.log(Object.isFrozen(person)); // true
上記の定義から分かる通り、そのオブジェクトがアクセサプロパティしか持っていなかった場合、シールした時点で、同時にフリーズさせたことにもなる。
var person = { get name(){ return; } }; Object.preventExtensions(person); console.log(Object.isExtensible(person)); // false console.log(Object.isSealed(person)); // false console.log(Object.isFrozen(person)); // false Object.seal(person); console.log(Object.isSealed(person)); // true console.log(Object.isFrozen(person)); // true