Deep JavaScript
この書籍へのご支援をお願いします: 購入 または 寄付
(広告、ブロックしないでください。)

10 オブジェクトが変更されるのを防ぐ



この章では、オブジェクトが変更されないように保護する方法を見ていきます。例としては、プロパティの追加を防止したり、プロパティの変更を防止したりすることがあります。

  必要な知識:プロパティ属性

この章では、プロパティ属性に精通している必要があります。もしそうでない場合は、§9「プロパティ属性:入門」を確認してください。

10.1 保護のレベル:拡張の防止、シーリング、フリーズ

JavaScriptには、オブジェクトを保護するための3つのレベルがあります。

10.2 オブジェクトの拡張を防止する

Object.preventExtensions<T>(obj: T): 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), []);

10.2.1 オブジェクトが拡張可能かどうかを確認する

Object.isExtensible(obj: any): boolean

objが拡張可能かどうかを確認します。例として

> const obj = {};
> Object.isExtensible(obj)
true
> Object.preventExtensions(obj)
{}
> Object.isExtensible(obj)
false

10.3 オブジェクトをシーリングする

Object.seal<T>(obj: T): T

このメソッドの説明

次の例は、シーリングによってオブジェクトが非拡張可能になり、そのプロパティが非構成可能になることを示しています。

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の値はまだ変更できます。

obj.first = 'John';
assert.deepEqual(
  obj, {first: 'John', last: 'Doe'});

しかし、その属性は変更できません。

assert.throws(
  () => Object.defineProperty(obj, 'first', { enumerable: false }),
  /^TypeError: Cannot redefine property: first$/);

10.3.1 オブジェクトがシーリングされているかどうかを確認する

Object.isSealed(obj: any): boolean

objがシーリングされているかどうかを確認します。例として

> const obj = {};
> Object.isSealed(obj)
false
> Object.seal(obj)
{}
> Object.isSealed(obj)
true

10.4 オブジェクトをフリーズする

Object.freeze<T>(obj: T): T;
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$/);

10.4.1 オブジェクトがフリーズされているかどうかを確認する

Object.isFrozen(obj: any): boolean

objがフリーズされているかどうかを確認します。例として

> const point = { x: 17, y: -5 };
> Object.isFrozen(point)
false
> Object.freeze(point)
{ x: 17, y: -5 }
> Object.isFrozen(point)
true

10.4.2 フリーズは浅い

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'],
  });

10.4.3 ディープフリーズを実装する

ディープフリーズが必要な場合は、自分で実装する必要があります。

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$/);

10.5 さらに読む