この章では、オブジェクトが変更されないように保護する方法を見ていきます。例としては、プロパティの追加を防止したり、プロパティの変更を防止したりすることがあります。
必要な知識:プロパティ属性
この章では、プロパティ属性に精通している必要があります。もしそうでない場合は、§9「プロパティ属性:入門」を確認してください。
JavaScriptには、オブジェクトを保護するための3つのレベルがあります。
Object.preventExtensions(obj)
Object.seal(obj)
Object.freeze(obj)
このメソッドは次のように機能します。
obj
がオブジェクトでない場合は、それを返します。obj
を変更し、それを返します。<T>
は、結果がパラメータと同じ型であることを表します。例でObject.preventExtensions()
を使用してみましょう。
const obj = { first: 'Jane' };
Object.preventExtensions(obj);
assert.throws(
() => obj.last = 'Doe',
/^TypeError: Cannot add property last, object is not extensible$/);
ただし、プロパティを削除することはまだ可能です。
assert.deepEquals(
Object.keys(obj), ['first']);
delete obj.first;
assert.deepEquals(
Object.keys(obj), []);
obj
が拡張可能かどうかを確認します。例として
> const obj = {};
> Object.isExtensible(obj)
true
> Object.preventExtensions(obj)
{}
> Object.isExtensible(obj)
false
このメソッドの説明
obj
がオブジェクトでない場合は、それを返します。obj
の拡張を防止し、そのすべてのプロパティを非構成可能にし、それを返します。プロパティが非構成可能であるということは、それら(値を除いて)をもう変更できないことを意味します。読み取り専用プロパティは読み取り専用のまま、列挙可能なプロパティは列挙可能なままなどです。次の例は、シーリングによってオブジェクトが非拡張可能になり、そのプロパティが非構成可能になることを示しています。
const obj = {
first: 'Jane',
last: 'Doe',
};
// Before sealing
assert.equal(Object.isExtensible(obj), true);
assert.deepEqual(
Object.getOwnPropertyDescriptors(obj),
{
first: {
value: 'Jane',
writable: true,
enumerable: true,
configurable: true
},
last: {
value: 'Doe',
writable: true,
enumerable: true,
configurable: true
}
});
Object.seal(obj);
// After sealing
assert.equal(Object.isExtensible(obj), false);
assert.deepEqual(
Object.getOwnPropertyDescriptors(obj),
{
first: {
value: 'Jane',
writable: true,
enumerable: true,
configurable: false
},
last: {
value: 'Doe',
writable: true,
enumerable: true,
configurable: false
}
});
プロパティ.first
の値はまだ変更できます。
しかし、その属性は変更できません。
assert.throws(
() => Object.defineProperty(obj, 'first', { enumerable: false }),
/^TypeError: Cannot redefine property: first$/);
obj
がシーリングされているかどうかを確認します。例として
obj
を返します。obj
をシーリングして返します。つまり、obj
は拡張不可能であり、すべてのプロパティは読み取り専用であり、それを変更する方法はありません。const point = { x: 17, y: -5 };
Object.freeze(point);
assert.throws(
() => point.x = 2,
/^TypeError: Cannot assign to read only property 'x'/);
assert.throws(
() => Object.defineProperty(point, 'x', {enumerable: false}),
/^TypeError: Cannot redefine property: x$/);
assert.throws(
() => point.z = 4,
/^TypeError: Cannot add property z, object is not extensible$/);
obj
がフリーズされているかどうかを確認します。例として
> const point = { x: 17, y: -5 };
> Object.isFrozen(point)
false
> Object.freeze(point)
{ x: 17, y: -5 }
> Object.isFrozen(point)
true
Object.freeze(obj)
は、obj
とそのプロパティのみをフリーズします。それらのプロパティの値はフリーズしません。例として
const teacher = {
name: 'Edna Krabappel',
students: ['Bart'],
};
Object.freeze(teacher);
// We can’t change own properties:
assert.throws(
() => teacher.name = 'Elizabeth Hoover',
/^TypeError: Cannot assign to read only property 'name'/);
// Alas, we can still change values of own properties:
teacher.students.push('Lisa');
assert.deepEqual(
teacher, {
name: 'Edna Krabappel',
students: ['Bart', 'Lisa'],
});
ディープフリーズが必要な場合は、自分で実装する必要があります。
function deepFreeze(value) {
if (Array.isArray(value)) {
for (const element of value) {
deepFreeze(element);
}
Object.freeze(value);
} else if (typeof value === 'object' && value !== null) {
for (const v of Object.values(value)) {
deepFreeze(v);
}
Object.freeze(value);
} else {
// Nothing to do: primitive values are already immutable
}
return value;
}
前のセクションの例を再検討すると、deepFreeze()
が実際に深くフリーズしているかどうかを確認できます。
const teacher = {
name: 'Edna Krabappel',
students: ['Bart'],
};
deepFreeze(teacher);
assert.throws(
() => teacher.students.push('Lisa'),
/^TypeError: Cannot add property 1, object is not extensible$/);