「プロパティ」は「値+属性」のセット
これまで:
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 から見るときの名前が、それぞれ
valuewritableenumerableconfigurable
です。
アクセサプロパティ(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種類は混ぜられません(value と get を同時に持たせることはできない)。
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: trueenumerable: trueconfigurable: 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を書いていないので全て falseobj.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: xconfigurable: 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)); // truepreventExtensions+全プロパティを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など)がありますが、最初はこのイメージで十分です。
コメント