We're looking for a user researcher to understand the needs of developers and designers. Is this you or someone you know? Check out the post: https://mzl.la/2IGzdXS

指定されたオブジェクトに新しいプロパティを定義したり、既存のプロパティを変更したりします。戻り値は指定されたオブジェクトがそのまま返ります。

Note: このメソッドは Object コンストラクターを使って直接呼ぶものです。Object インスタンスで呼ぶものではありません。

構文

Object.defineProperty(obj, prop, descriptor)

引数

obj
プロパティを定義したいオブジェクト。
prop
定義または変更するプロパティの名前。
descriptor
定義または変更されるプロパティのディスクリプタ。

戻り値

渡されたオブジェクトをそのまま返します。

説明

このメソッドで、オブジェクト上でプロパティを明示的に追加または変更することができます。代入による通常のプロパティ追加では、プロパティ列挙 (for...inループやObject.keysメソッド) に現れ、値を変更可能で、また削除も可能なプロパティを生成します。このメソッドでは、これらの詳細事項を既定値から変えることが可能です。By default, values added using Object.defineProperty() are immutable.

プロパティのディスクリプタは主に 2 種類あります: データディスクリプタと、アクセサディスクリプタです。データディスクリプタ は値を持つプロパティで、その値は書き換え可能にも不可能にできます。アクセサディスクリプタ は、関数の getter と setter の組で表されるプロパティです。ディスクリプタはこれら 2 種類のいずれかでなければなりません。つまり、両方の形を取ることはできません。

どちらの形でもディスクリプタはオブジェクトで表現します。共通して以下のキーを持つことが出来ます。

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, getset のいずれのキーもない場合データディスクリプタとして扱われます。value か writableget か set の両方のキーを持っている場合は例外が投げられます。

これらのキーは必ずしもディスクリプタが直接所有しているとは限らないことに留意して下さい。ディスクリプタが継承しているキーも参照されるためです。上で説明しているディスクリプタのキーのデフォルト値が確実に適用されるようにするためには、Object.prototype を先回りして freeze() しておくか、すべてのディスクリプタキーを明示的に指定するか、Object.create(null) でprototypeが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: null
    }
  );
  d.value = value;
  return d;
}
// このように使います。
Object.defineProperty(obj, 'key', withValue('static'));

// freeze() が使えるなら、Object.prototype にプロパティ(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

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

Configurable 属性

configurable 属性は、プロパティをオブジェクトから削除できるかとプロパティの属性 (valuewritable 以外) を変更できるかを同時に制御します。

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 が発生

console.log(o.a); // logs 1
delete o.a; // 何も起きません
console.log(o.a); // logs 1

o.aconfigurable 属性が 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 に格納されるわけではありません。しかし、non-writable なデータプロパティを継承している場合、継承先オブジェクトでも書き換えは阻止されます。

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

Specifications

Specification Status Comment
ECMAScript 5.1 (ECMA-262)
Object.defineProperty の定義
標準 Initial definition. Implemented in JavaScript 1.8.5.
ECMAScript 2015 (6th Edition, ECMA-262)
Object.defineProperty の定義
標準  
ECMAScript Latest Draft (ECMA-262)
Object.defineProperty の定義
ドラフト  

Browser compatibility

機能ChromeEdgeFirefoxInternet ExplorerOperaSafari
基本対応5 あり49111.65.12
機能Android webviewChrome for AndroidEdge mobileFirefox for AndroidOpera AndroidiOS SafariSamsung Internet
基本対応 あり あり あり411.5 あり あり

1. Also supported in Internet Explorer 8, but only on DOM objects and with some non-standard behaviors.

2. Also supported in Safari 5, but not on DOM objects.

クロスブラウザに於けるの懸念事項

Array オブジェクトの length プロパティの再定義

配列の length プロパティを再定義することは可能です。通常の再定義方法と同じです。(The length property is initially non-configurable, non-enumerable, and writable. Thus on an unaltered array, it's possible to change the length property's value or to make it non-writable. It is not allowed to change its enumerability or configurability, or if it is non-writable to change its value or writability.) ただし、すべてのブラウザでこの再定義が可能ではありません。

Firefox 4 through 22 will throw a TypeError on any attempt whatsoever (whether permitted or not) to redefine the length property of an array.

Object.defineProperty() を実装する Chrome のバージョンでは一部の状況において、配列の現在の length プロパティと異なる値の length を無視し、また書き込み許可の変更は一部の状況において暗黙的に動作しないようです。 (and not throw an exception). Also, relatedly, some array-mutating methods like Array.prototype.push don't respect a non-writable length.

Object.defineProperty() を実装する Safari のバージョンでは配列の現在の length プロパティと異なる値の length を無視し、また書き込み許可を変更する試みはエラーなしに実行されますが実際はプロパティの書き込み許可が変更されません。

Internet Explorer 9 以降と Firefox 23 以降のみが、完全かつ正確に配列の length プロパティの再定義を実装しているようです。現時点では、配列の length プロパティの再定義はどのブラウザでも動作する、あるいは特定のルールに則って動作するとは考えないようにしてください。there's really no good reason to do so.

Internet Explorer 8 特有のケース

Internet Explorer 8 は Object.defineProperty() メソッドを DOM オブジェクトでのみ使用できるものとして実装しました。以下 2 点に注意が必要です:

  • ネイティブオブジェクトで Object.defineProperty() を用いようとするとエラーが発生します。
  • プロパティの属性は特定の値を設定しなければなりません。データディスクリプタでは true, true, true、アクセサディスクリプタでは configurable に true、enumerable に false です。ほかの値を与えようとすると、エラーが発生します。
  • プロパティの再設定には、始めにプロパティの削除が必要です。プロパティが削除されていない場合、再設定を試みる前の状態のままになります。

関連情報

ドキュメントのタグと貢献者

このページの貢献者: woodmix, taiyaki32p, YuichiNukiyama, teoli, ethertank, yyss
最終更新者: woodmix,