ECMA-262 5th edition で導入された Object.defineProperty を使い、属性を指定してプロパティを定義する
ECMAScript *1 において、プロパティとは名前と値のペアを意味します。 より詳しく言うと、名前が付けられたプロパティは、その状態を表す属性 (Property Attribute) *2 の集合を値として持っています。
ECMA-262 5th edition では、この属性の値を指定してプロパティを定義できるように、Object.defineProperty
というメソッドが導入されました。 この記事では、Object.defineProperty
メソッドを使い、属性を指定してプロパティを定義する方法を説明します。
なお、ECMA-262 5th edition を実装している JavaScript 処理系はまだ多くありません。 Firefox 4 などの一部のブラウザなどでしか実行できません。 また、サンプルコード中で、結果を出力するための print
関数を使用していますが、適当に出力用の関数に置き換えてください。
プロパティの属性
プロパティにはそれぞれ属性があり、読み出し可能かどうかや for-in ループ中にそのプロパティが現れるかどうかが決定されます。 ECMAScript の 5th edition で導入された accessor property *3 と、昔ながらの data property とでは異なる属性を持ちます。 それぞれのプロパティがどのような属性を持つのかを次に説明します。
data property が持つ属性
属性名 | 意味 |
---|---|
[[Value]] | プロパティの値。 |
[[Writable]] | 内部関数 [[Put]] *4 によって [[Value]] の値を書き換えられるかどうか。 true なら書き換え可能。 |
[[Enumerable]] | true か false 。 true なら for-in ループ中に値が現れる。 false なら現れない。 |
[[Configurable]] | true か false 。 false ならプロパティを delete したり、data property に変化させたり、プロパティの属性 ([[Value]] を除く) を変更したりできない。 |
accessor property が持つ属性
属性名 | 意味 |
---|---|
[[Get]] | Undefined か関数。 プロパティの値が参照されたときに呼び出される。 |
[[Set]] | Undefined か関数。 プロパティへの値の代入が行われるときに呼び出される。 |
[[Enumerable]] | true か false 。 true なら for-in ループ中に値が現れる。 false なら現れない。 |
[[Configurable]] | true か false 。 false ならプロパティを delete したり、data property に変化させたり、プロパティの属性を変更したりできない。 |
Object.defineProperty
メソッドを使用してプロパティを定義する
さて、普通にプロパティを定義しただけでは、これらの属性を指定することはできません。 これらの属性を指定してプロパティを定義するには、Object.defineProperty
メソッドを使用します。 このメソッドは引数を 3 つとり、第 1 引数がプロパティを設定する対象となるオブジェクト、第 2 引数が定義するプロパティの名前、第 3 引数がプロパティの属性を指定するためのオブジェクト、となります。 第 3 引数は、プロパティで属性を指定します。 例えば、[[Value]] が 10
で、[[Enumerable]] が true
である新しいプロパティを作る場合は、{ value: 3, enumerable: true }
というオブジェクトを渡すことになります。
新しいプロパティが accessor property になるか data property になるかは、第 3 引数の内容によって決まります。 第 3 引数として渡すオブジェクトに "value" や "writable" という名前のプロパティがあればそれは data property になりますし、"set" や "get" という名前のプロパティがあればそれは accessor property になります。 なお、"value" も "set" も持っているようなオブジェクトを引数として渡すとエラーが発生します。
var obj = {}; // 書き込み不可能な新しい data property を定義する Object.defineProperty( obj, "test", { value: 200, writable: false } ); // 新しいプロパティを参照 print( obj.test ); // 200 // 書き込みしてみる obj.test = 300; // しかし、書き込みは反映されず、200 のまま print( obj.test ); // 200
第 3 引数に渡すオブジェクトのプロパティは、一部 (または全部) 省略できます。 省略した際のデフォルトの値は次のとおりです。
属性名 | デフォルト値 |
---|---|
[[Get]] | Undefined |
[[Set]] | Undefined |
[[Value]] | Undefined |
[[Writable]] | false |
[[Enumerable]] | false |
[[Configurable]] | false |
既存のオブジェクトに accessor property を定義する
前の記事 で述べた accessor property を既存のオブジェクトに定義するためにも Object.defineProperty
を使います。
例として、値の代入を行う際に、値の範囲をチェックするような accessor property を定義してみます。 ここでは、accessor property が値を格納するための変数 (value
) を、クロージャを使って確保しています。
var obj = {}; // accessor property を定義 Object.defineProperty( obj, "prop1", (function() { var value; return { set: function( v ) { if( 0 <= v && v <= 100 ) { value = v } else { throw new Error( "値は 0 から 100 までの範囲で指定してください" ) } }, get: function() { return value } } } )() ); // accessor property を使う obj.prop1 = 10; // 値の設定 print( obj.prop1 ); // 値の読み出し obj.prop1 = 1000 // 期待されない値を設定しようとすると例外が投げられる
まとめ
- プロパティは属性を持っている
- ECMAScript 5th edition から導入された
Object.defineProperty
メソッドを使えば、属性を指定してプロパティを定義できる - 第 3 引数に渡すオブジェクトのプロパティによって data property になるか accessor property になるかが決められる
Object.defineProperty
が使える JavaScript 処理系はまだ多くない