[JavaScript講座] プロパティ属性とディスクリプタ

当ページのリンクには広告が含まれています。
目次

「プロパティ」は「値+属性」のセット

これまで:

const obj = { x: 1 };

と書くとき、「x というキーに 1 が入っている」というイメージでしたが、
仕様的に言うと、プロパティは概ねこんな情報を持っています:

  • キー("x"
  • 値 or getter/setter
  • 属性(attributes)
    • writable:値を書き換えられるか?
    • enumerable:列挙(for…in / Object.keys など)に出てくるか?
    • configurable:削除や属性変更が許されるか?

重要ポイント:

JavaScript の「オブジェクト設計」を理解する=この「プロパティ属性」を使いこなせるようになること

と言っていいくらい、根幹の概念です。

データプロパティ vs アクセサプロパティ

プロパティには 2 種類あります。

データプロパティ(普通の「値」付きプロパティ)

const obj = { x: 1 };
// x は「データプロパティ」

内部的には以下の情報を持っています。

  • [[Value]]:実際の値(ここでは 1)
  • [[Writable]]:書き換え可能か(true/false)
  • [[Enumerable]]:列挙対象か
  • [[Configurable]]:設定変更・削除可能か

これを JS から見るときの名前が、それぞれ

  • value
  • writable
  • enumerable
  • configurable

です。

アクセサプロパティ(getter / setter を持つプロパティ)

const obj = {
  _value: 1,
  get value() {
    return this._value;
  },
  set value(v) {
    this._value = v;
  },
};

この value プロパティは、内部的には:

  • [[Get]]:getter 関数
  • [[Set]]:setter 関数
  • [[Enumerable]]
  • [[Configurable]]

を持つ アクセサプロパティ です。

  • アクセサプロパティには value / writable は存在しない
  • データプロパティには get / set は存在しない

この2種類は混ぜられません(valueget を同時に持たせることはできない)。

Object.getOwnPropertyDescriptor で属性を覗く

const obj = { x: 1 };

console.log(Object.getOwnPropertyDescriptor(obj, "x"));
/*
{
  value: 1,
  writable: true,
  enumerable: true,
  configurable: true
}
*/
  • 普通に obj.x = 1 / { x: 1 } で作ったプロパティは、
    • writable: true
    • enumerable: true
    • configurable: true
      がデフォルト

アクセサプロパティも覗いてみる:

const obj = {
  _value: 1,
  get value() {
    return this._value;
  },
  set value(v) {
    this._value = v;
  },
};

console.log(Object.getOwnPropertyDescriptor(obj, "value"));
/*
{
  get: [Function: get value],
  set: [Function: set value],
  enumerable: true,
  configurable: true
}
*/
  • value, writable の代わりに get, set を持っているのが分かります。

Object.defineProperty で属性をコントロールする

基本書式

Object.defineProperty(obj, "propName", {
  // データプロパティの場合
  value: 123,
  writable: false,
  enumerable: false,
  configurable: false,
});

// またはアクセサプロパティの場合
Object.defineProperty(obj, "propName", {
  get() { ... },
  set(value) { ... },
  enumerable: true,
  configurable: true,
});

注意点(よくハマるポイント):

明示しなかった属性は デフォルトで false(or undefined) になる

これがかなり罠です。

例:writable / enumerable / configurable が全部 false になる例

const obj = {};

Object.defineProperty(obj, "x", { value: 1 });

console.log(Object.getOwnPropertyDescriptor(obj, "x"));
/*
{
  value: 1,
  writable: false,
  enumerable: false,
  configurable: false
}
*/
  • writable, enumerable, configurable を書いていないので全て false
  • obj.x = 2 しようとしても無視される(strict なら TypeError)
  • for...in などにも出てこない
  • delete obj.x もできない

→ もし通常のプロパティに近づけたいなら、明示的に true を指定する必要があります:

Object.defineProperty(obj, "x", {
  value: 1,
  writable: true,
  enumerable: true,
  configurable: true,
});

各属性の意味を具体的に見る

writable:書き換え可能か

"use strict";

const obj = {};
Object.defineProperty(obj, "x", {
  value: 1,
  writable: false,
  enumerable: true,
  configurable: true,
});

console.log(obj.x); // 1
obj.x = 2;          // strictモードなら TypeError: Cannot assign to read only property 'x'
console.log(obj.x); // 1 のまま
  • writable: false → そのプロパティの「値」を書き換えられない
  • アクセサプロパティには writable はありません(setter を定義するかどうかで決まる)

enumerable:列挙対象か

const obj = {};
Object.defineProperty(obj, "hidden", {
  value: "secret",
  enumerable: false,
  configurable: true,
  writable: true,
});

obj.visible = "hello";

console.log(Object.keys(obj)); // ["visible"]
for (const key in obj) {
  console.log(key);            // visible
}

console.log(obj.hidden);       // "secret" (直接アクセスは普通にできる)
  • enumerable: false → 列挙には出さないけど、通常アクセスはできる
  • ライブラリ内部で「内部用フラグ」「メタ情報」を隠したいときなどに使う

実際、組み込みメソッドたちも多くが enumerable: false です。

configurable:削除・再定義できるか

"use strict";

const obj = {};
Object.defineProperty(obj, "x", {
  value: 1,
  writable: true,
  enumerable: true,
  configurable: false,
});

// 削除できない
delete obj.x;      // false(strictなら TypeError)
console.log(obj.x); // 1

// 再定義もできない
Object.defineProperty(obj, "x", {
  value: 2,
});                // TypeError: Cannot redefine property: x
  • configurable: false になると:
    • delete 不可
    • 属性(writable/enumerable/configurable)の変更不可
      ※ただし、writable を true → false にすることだけは 1 回だけ許される、など細かい仕様がありますが、最初は「基本的にいじれなくなる」と覚えてOK

オブジェクトの「凍結レベル」を変える 3 つの関数

Object.freeze, Object.seal, Object.preventExtensions

それぞれの違いを整理します。

Object.preventExtensions:新しいプロパティの追加禁止

const obj = { x: 1 };

Object.preventExtensions(obj);

obj.y = 2;          // 失敗(strict なら TypeError)
console.log(obj.y); // undefined

console.log(Object.isExtensible(obj)); // false
  • 新しいプロパティの追加だけ禁止
  • 既存のプロパティはそのまま(削除もできるし、書き換えもできる)
  • 属性も変えられる(configurable である限り)

イメージ:
「これ以上“出入口”を増やさない」

Object.seal:追加と削除を禁止(値は書き換え可)

const obj = { x: 1, y: 2 };

Object.seal(obj);

delete obj.x;      // 失敗
obj.y = 999;       // OK

console.log(obj);  // { x: 1, y: 999 }

console.log(Object.isExtensible(obj)); // false
console.log(Object.isSealed(obj));     // true
  • preventExtensions +全プロパティを configurable: false にする

その結果:

  • プロパティの削除ができなくなる
  • プロパティの再定義(属性変更)が基本できなくなる(writable: true → false への変更だけ例外的に許される)
  • ただし writable: true のプロパティなら、値の書き換えはできる

イメージ:
「出入口を増やさない + 既にある出入口を外すこともできない」

Object.freeze:完全に凍結(値も変更不可)

"use strict";

const obj = { x: 1, y: 2 };

Object.freeze(obj);

obj.x = 999;    // TypeError(strict)
delete obj.y;   // TypeError(strict)

console.log(obj); // { x: 1, y: 2 }

console.log(Object.isFrozen(obj)); // true
  • seal に加えて、データプロパティの writable も false にする
  • つまり:
    • 新しいプロパティ追加 → 不可
    • 既存プロパティ削除 → 不可
    • 値の書き換え → 不可
    • 属性変更 → 原則不可

イメージ:
「完全に固めた定数オブジェクト」

注意点:

  • ネストしたオブジェクトまでは自動で凍結されない
const obj = {
  nested: { x: 1 },
};

Object.freeze(obj);

obj.nested.x = 999; // これは普通に通る(nested 自体は凍結していない)

完全に凍結させたい場合は、再帰的に freeze をかける「deepFreeze」関数を自作する必要があります。

ざっくりまとめ表

よくある 4 パターンをまとめると:

状態新規プロパティ追加既存プロパティ削除値の書き換え属性変更
通常
preventExtensions(obj)不可
seal(obj)不可不可(writableなら)可ほぼ不可
freeze(obj)不可不可不可ほぼ不可

※「属性変更ほぼ不可」のところは、細かい例外(writable: true → false だけOKなど)がありますが、最初はこのイメージで十分です。


<<前へ(クラスフィールドとプライベートフィールド)

>>次へ(文字列・数値・数学)

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

CAPTCHA


目次