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]] truefalsetrue なら for-in ループ中に値が現れる。 false なら現れない。
[[Configurable]] truefalsefalse ならプロパティを delete したり、data property に変化させたり、プロパティの属性 ([[Value]] を除く) を変更したりできない。
accessor property が持つ属性
属性名 意味
[[Get]] Undefined か関数。 プロパティの値が参照されたときに呼び出される。
[[Set]] Undefined か関数。 プロパティへの値の代入が行われるときに呼び出される。
[[Enumerable]] truefalsetrue なら for-in ループ中に値が現れる。 false なら現れない。
[[Configurable]] truefalsefalse ならプロパティを 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 処理系はまだ多くない

*1:JavaScript やそれに類似の言語を統一した言語。

*2:ECMA-262 5th edition の 8.6.1 節参照。

*3:accessor property については 前の記事 で詳しく述べている。

*4:プロパティへの値の代入時などに呼び出される。