読者です 読者をやめる 読者になる 読者になる

オブジェクトの変更防止

JavaScriptの言語仕様(ES5)

オブジェクトも内部属性を持っている。
その一つが、[[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