Object.defineProperty()
静的メソッドの Object.defineProperty()
は、あるオブジェクトに新しいプロパティを直接定義したり、オブジェクトの既存のプロパティを変更したりして、そのオブジェクトを返します。
メモ: このメソッドは Object
コンストラクターで直接呼び出すものであって、Object
型のインスタンスで呼ぶものではありません。
The source for this interactive example is stored in a GitHub repository. If you'd like to contribute to the interactive examples project, please clone https://github.com/mdn/interactive-examples and send us a pull request.
構文
Object.defineProperty(obj, prop, descriptor)
引数
obj
- プロパティを定義したいオブジェクト。
prop
- 定義または変更するプロパティの名前または
Symbol
。 descriptor
- 定義または変更されるプロパティの記述子。
返値
渡されたオブジェクトをそのまま返します。
解説
このメソッドで、あるオブジェクトのプロパティを明示的に追加または変更することができます。代入による通常のプロパティ追加では、プロパティ列挙 (for...in
ループや Object.keys
メソッド) に現れ、値は変更可能で、また削除も可能なプロパティが生成されます。このメソッドでは、これらの詳細事項を既定値から変えることが可能です。既定では、Object.defineProperty()
を使って追加された値は不変になります。
プロパティの記述子は、データ記述子とアクセサー記述子の二つに分かれます。データ記述子は値を持つプロパティで、その値は書き換え可能にも不可能にもできます。アクセサー記述子は、関数のゲッターとセッターの組で表されるプロパティです。記述子はこれら二種類のどちらかでなければならず、両方になることはできません。
どちらの形でも記述子はオブジェクトで表現します。共通して以下のキーを持つことができます (既定値は Object.defineProperty() を使ってプロパティを定義する場合)。
configurable
true
である場合のみ、この種の記述子を変更することや、対応するオブジェクトからプロパティを削除することができます。
既定値はfalse
です。enumerable
true
である場合のみ、このプロパティは対応するオブジェクトでのプロパティ列挙に現れます。
既定値はfalse
です。
データ記述子の場合はオプションとして次のキーを持つことができます:
value
- プロパティに関連づけられた値です。有効な JavaScript の値 (number, object, function など) である必要があります。
既定値はundefined
です。 writable
true
である場合のみ、プロパティに関連づけられた値は代入演算子で変更することができます。
既定値はfalse
です。
アクセサー記述子の場合はオプションとして次のキーを持つことができます。
get
- プロパティのゲッターとなる関数で、ゲッターを設けない場合は
undefined
です。プロパティにアクセスするとこの関数が引数なしでコールされます。この関数内でthis
はアクセスしようとしたプロパティを持つオブジェクトになります(プロパティを定義するために作成した記述子オブジェクトではありません)。戻り値はこのプロパティの値として使われます。
既定値はundefined
です。 set
- プロパティのセッターとなる関数で、セッターがない場合は
undefined
です。プロパティに値が割り当てられたとき、その値を引数としてこの関数がコールされます。この関数内でthis
は割り当てようとしたプロパティを持つオブジェクトになります。
既定値はundefined
です。
記述子に value
, writable
, get
, set
のいずれのキーもない場合、データ記述子として扱われます。記述子に value
または writable
と、get
または set
のキーの両方がある場合は、例外が投げられます。
これらのキーは必ずしも記述子が直接所有しているとは限らないことに留意してください。継承されたプロパティも同様です。これらの既定値が存在することを保証するには、先行して Object.prototype
を freeze しておくか、すべてのオプションを明示的に指定するか、Object.create(null)
で null
に設定するかします。
// __proto__ を使うやり方
var obj = {};
var descriptor = Object.create(null); // 意図しないキーの継承を防止します。
descriptor.value = 'static';
// 既定で継承不可、変更不可、書換不可のプロパティとなります。
Object.defineProperty(obj, 'key', descriptor);
// 明示的な指定
Object.defineProperty(obj, 'key', {
enumerable: false,
configurable: false,
writable: false,
value: 'static'
});
// 同じオブジェクトを再利用
function withValue(value) {
var d = withValue.d || (
withValue.d = {
enumerable: false,
writable: false,
configurable: false,
value: value
}
);
// 値の代入で重複操作を防ぐ
if (d.value !== value) d.value = value;
return d;
}
// このように使います。
Object.defineProperty(obj, 'key', withValue('static'));
// freeze が利用できるなら、オブジェクトのプロトタイプのプロパティ
// (value, get, set, enumerable, writable, configurable) を
// 追加・削除することを防ぐことができます。
(Object.freeze || Object)(Object.prototype);
例
バイナリーフラグを使って Object.defineProperty
を利用したい場合は 追加の例 を見てください。
プロパティの生成
オブジェクトに指定されたプロパティが存在しないとき、Object.defineProperty()
は指定された形で新たなプロパティを生成します。記述子のキーは省略することができ、そのようなキーには既定値が適用されます。
var o = {}; // 新しいオブジェクトの生成
// データ記述子により、defineProperty を用いて
// オブジェクトプロパティを追加する例
Object.defineProperty(o, 'a', {
value: 37,
writable: true,
enumerable: true,
configurable: true
});
// o オブジェクトに 'a' プロパティが存在するようになり、その値は 37 となります
// アクセサー記述子により、defineProperty を用いて
// オブジェクトプロパティを追加する例
var bValue = 38;
Object.defineProperty(o, 'b', {
// メソッド名ショートハンドを利用しています(ES2015 の機能)。
// 次のように書いているのと同じことです:
// get: function() { return bValue; },
// set: function(newValue) { bValue = newValue; },
get() { return bValue; },
set(newValue) { bValue = newValue; },
enumerable: true,
configurable: true
});
o.b; // 38
// o オブジェクトに 'b' プロパティが存在するようになり、
// その値は 38 となります
// o.b は再定義されない限り、その値は常に bValue と同じです。
// (訳注:データとアクセサーを)両方を混在させることはできません:
Object.defineProperty(o, 'conflict', {
value: 0x9f91102,
get() { return 0xdeadbeef; }
});
// TypeError が発生します。value はデータ記述子にのみ、
// get はアクセサー記述子にのみ存在していなければなりません。
プロパティの変更
プロパティが既に存在している場合、Object.defineProperty()
は記述子の値および現在のオブジェクトの設定に基づいて、プロパティの変更を試みます。元の記述子で configurable
属性が false
なら、そのプロパティは「変更不可」です。変更不可のプロパティは記述子の属性を変更することができません。データプロパティで writable
なら、値を変更することができますし、writable
属性を true
から false
に変更することが出来ます。変更不可のプロパティはデータとアクセサーの種別を切り替えることはできません。
変更不可なプロパティに変更を加えようとすると、新旧の値が同じでない限り TypeError
が投げられます(可能な場合の value
と writable
の変更は除きます)。
Writable 属性
writable
プロパティ属性が false
に設定されているとき、そのプロパティは書換不可になります。代入が出来なくなります。
var o = {}; // 新しいオブジェクトの生成
Object.defineProperty(o, 'a', {
value: 37,
writable: false
});
console.log(o.a); // 37 がログ出力されます
o.a = 25; // エラーは発生しません
// (strict モードでは発生します。同じ値を代入したとしても。)
console.log(o.a); // 37 がログ出力されます。代入文は動作しません。
// strict mode
(function() {
'use strict';
var o = {};
Object.defineProperty(o, 'b', {
value: 2,
writable: false
});
o.b = 3; // TypeError がスローされます: "b" is read-only
return o.b; // 上の行は動作せず 2 が返ります(訳注:正しくは「ここに制御は来ません」)
}());
例で見たように、書き込み不可のプロパティに書き込もうとしても変更されず、またエラーは発生しません。
Enumerable 属性
enumerable
プロパティ属性は、プロパティが for...in
ループや Object.keys()
に現れるか否かを定義します。
var o = {};
Object.defineProperty(o, 'a', {
value: 1,
enumerable: true
});
Object.defineProperty(o, 'b', {
value: 2,
enumerable: false
});
Object.defineProperty(o, 'c', {
value: 3
}); // enumerable の既定値は false
o.d = 4; // このようにプロパティを生成するとき、
// enumerable の既定値は true
Object.defineProperty(o, Symbol.for('e'), {
value: 5,
enumerable: true
});
Object.defineProperty(o, Symbol.for('f'), {
value: 6,
enumerable: false
});
for (var i in o) {
console.log(i);
}
// 'a' と 'd' がログされます(順不同)
Object.keys(o); // ['a', 'd']
o.propertyIsEnumerable('a'); // true
o.propertyIsEnumerable('b'); // false
o.propertyIsEnumerable('c'); // false
o.propertyIsEnumerable('d'); // true
o.propertyIsEnumerable(Symbol.for('e')); // true
o.propertyIsEnumerable(Symbol.for('f')); // false
var p = { ...o }
p.a // 1
p.b // undefined
p.c // undefined
p.d // 4
p[Symbol.for('e')] // 5
p[Symbol.for('f')] // undefined
Configurable 属性
configurable
属性は、プロパティをオブジェクトから削除できるかとプロパティの属性 (value
と writable
以外) を変更できるかを同時に制御します。
var o = {};
Object.defineProperty(o, 'a', {
get() { return 1; },
configurable: false
});
Object.defineProperty(o, 'a', {
configurable: true
}); // TypeError が発生
Object.defineProperty(o, 'a', {
enumerable: true
}); // TypeError が発生
Object.defineProperty(o, 'a', {
set() {}
}); // TypeError が発生 (set は未定義であった)
Object.defineProperty(o, 'a', {
get() { return 1; }
}); // TypeError が発生 (新たな get は全く同じであるにもかかわらず)
Object.defineProperty(o, 'a', {
value: 12
}); // TypeError が発生 ('configurable' が false でも 'value' は変更できますが、ここでは 'get' アクセサーがあるため変更できません)
console.log(o.a); // logs 1
delete o.a; // 何も起きません
console.log(o.a); // logs 1
o.a
の configurable
属性が true
である場合、エラーが発生することなく最終的にプロパティが削除されます。
プロパティおよび既定値の追加
属性の既定値がどう適用されるかを考えることは重要です。値の割り当てにドット表記を用いた場合と Object.defineProperty()
を用いた場合とでは、以下の例で示したとおりに違いがあります。
var o = {};
o.a = 1;
// これは以下と同じです:
Object.defineProperty(o, 'a', {
value: 1,
writable: true,
configurable: true,
enumerable: true
});
// その一方で、
Object.defineProperty(o, 'a', { value: 1 });
// これは以下と同じです:
Object.defineProperty(o, 'a', {
value: 1,
writable: false,
configurable: false,
enumerable: false
});
独自のゲッターおよびセッター
例として自律的に記録を行うオブジェクトを作成してみます。temperature
プロパティに値が代入されると、配列 archive
に要素が一つ追加されます。
function Archiver() {
var temperature = null;
var archive = [];
Object.defineProperty(this, 'temperature', {
get() {
console.log('get!');
return temperature;
},
set(value) {
temperature = value;
archive.push({ val: temperature });
}
});
this.getArchive = function() { return archive; };
}
var arc = new Archiver();
arc.temperature; // 'get!'
arc.temperature = 11;
arc.temperature = 13;
arc.getArchive(); // [{ val: 11 }, { val: 13 }]
次の例では、ゲッターが常に同じ値を返すようにしています。
var pattern = {
get() {
return 'I always return this string, ' +
'whatever you have assigned';
},
set() {
this.myname = 'this is my name string';
}
};
function TestDefineSetAndGet() {
Object.defineProperty(this, 'myproperty', pattern);
}
var instance = new TestDefineSetAndGet();
instance.myproperty = 'test';
console.log(instance.myproperty);
// I always return this string, whatever you have assigned
console.log(instance.myname); // this is my name string
プロパティの継承
アクセサープロパティを継承されると、その派生クラスでもプロパティがアクセスされたり書き換えられるときに get
と set
が呼ばれます。これらのメソッドが値を保持するために変数を使っていると、すべてのオブジェクトがその値を共有することになります。
function myclass() {
}
var value;
Object.defineProperty(myclass.prototype, "x", {
get() {
return value;
},
set(x) {
value = x;
}
});
var a = new myclass();
var b = new myclass();
a.x = 1;
console.log(b.x); // 1
この問題を回避する方法は値を別のプロパティで保持することです。get
と set
メソッド内で this
はアクセス/書き換えされようとしているプロパティを納めるオブジェクトを指しています。
function myclass() {
}
Object.defineProperty(myclass.prototype, "x", {
get() {
return this.stored_x;
},
set(x) {
this.stored_x = x;
}
});
var a = new myclass();
var b = new myclass();
a.x = 1;
console.log(b.x); // undefined
アクセサープロパティとは違い、データプロパティは常にオブジェクト自身に格納されるのであって、prototype に格納されるわけではありません。しかし、書き込み不可能なデータプロパティを継承している場合、継承先オブジェクトでも書き換えは阻止されます。
function myclass() {
}
myclass.prototype.x = 1;
Object.defineProperty(myclass.prototype, "y", {
writable: false,
value: 1
});
var a = new myclass();
a.x = 2;
console.log(a.x); // 2
console.log(myclass.prototype.x); // 1
a.y = 2; // 無視されます。strict モードではエラースローされます。
console.log(a.y); // 1
console.log(myclass.prototype.y); // 1
仕様書
ブラウザーの互換性
BCD tables only load in the browser
互換性のメモ
Array
オブジェクトの length
プロパティの再定義
配列の length
プロパティを再定義することは、通常の再定義の制限に照らせば可能です。(length
プロパティは初期状態で構成不可、列挙不可、書き込み可能です。つまり、変更されていない配列では、length
プロパティの値を変更したり書き込み不可にしたりすることが可能です。列挙可否や構成可否、また書き込み不可に変更した後は値や書き込み可否も、変更することはできません。) しかし、すべてのブラウザーがこの再定義を許可しているとは限りません。
Firefox 4 から 22 までの間では、配列の length
プロパティを再定義しようとすると、無条件に (許可の有無にかかわらず) TypeError
が発生します。
Object.defineProperty()
を実装している Chrome のバージョンでは、状況によっては配列の現在の length
プロパティとは異なる length の値を無視することがあります。状況によっては書き込み可否が暗黙に動作しない (そして例外を発生させない) こともあります。また、関連して、Array.prototype.push
のような配列を変更する一部のメソッドが、書き込み不可であることを尊重しないことがあります。
Object.defineProperty()
を実装する Safari のバージョンでは配列の現在の length
プロパティと異なる値の length を無視し、また書き込み許可を変更する試みはエラーなしに実行されますが、実際はプロパティの書き込み許可が変更されません。
Internet Explorer 9 以降と Firefox 23 以降のみが、完全かつ正確に配列の length
プロパティの再定義を実装しているようです。現時点では、配列の length
プロパティの再定義はどのブラウザーでも動作する、あるいは特定のルールに則って動作するとは考えないようにしてください。そして、もしこれが実行できたとしても、これを行う本当に良い理由はありません。
Internet Explorer 8 の特記事項
Internet Explorer 8 は Object.defineProperty()
メソッドを DOM オブジェクトでのみ使用できるものとして実装しました。以下 2 点に注意が必要です:
- ネイティブオブジェクトに対して
Object.defineProperty()
を用いようとするとエラーが発生します。 - プロパティの属性には特定の値を設定しなければなりません。
configurable
,enumerable
,writable
の各属性に対して、データ記述子ではすべてtrue
に、アクセサー記述子ではconfigurable
にtrue
、enumerable
にfalse
にです。(?)ほかの値(?)を与えようとすると、エラーが発生します。 - プロパティの再設定には、始めにプロパティの削除が必要です。プロパティが削除されていない場合、再設定を試みる前の状態のままになります。
Chrome 37 以下の特記事項
Chrome 37 以下には、writable: false
指定を行なった "prototype" プロパティを関数に定義する場合に、想定通りに動かない バグ があります。