Edited at

JavaScriptのプロパティディスクリプタ 〜 JSおくのほそ道 #019

More than 5 years have passed since last update.

こんにちは、ほそ道です。

今回は前回のGetter/Setterと関連深いプロパティディスクリプタを取り上げます。

プロパティディスクリプタとはオブジェクトのプロパティのメタ属性でデータアクセスに関する取り決めを保持しています。

MDNのjavascript1.8.5のドキュメントにまとまっていますが、ECMA5ではディスクリプタを設定してプロパティを制御するメソッドが沢山追加されています。

これはECMA5以降の仕様となっているようです。

目次はこちら


Object.getOwnPropertyDescriptor

ディスクリプタを見ていくにあたって、とりあえずgetOwnPropertyDescriptorというメソッドを動かしてみます。このメソッドはオブジェクトとプロパティを指定して、そのディスクリプタを取得できます。


とあるオブジェクト

var man = (function() {

var _age = 0;
return {
get age() {
return _age;
},
set age(val) {
_age = val;
}
}
}());

前回取り上げたようなGetter/Setterを持つオブジェクトです。

それではageプロパティのディスクリプタを取得してみましょう。


getOwnPropertyDescriptorその1

man.age = 100;

var descriptor = Object.getOwnPropertyDescriptor(man, 'age');
console.log(descriptor);
// 出力
// {get: [Function: age],
// set: [Function: age],
// enumerable: true,
// configurable: true }

今度はフツーにオブジェクトリテラルで宣言したプロパティのディスクリプタを取得してみましょう。


getOwnPropertyDescriptorその2

var foo = {

bar: 100
};

var descriptor = Object.getOwnPropertyDescriptor(foo, 'bar');
console.log(descriptor);

// 出力
// {value: 100,
// writable: true,
// enumerable: true,
// configurable: true }


ディスクリプタの内容が先ほどと異なりますね。

この違いについては下記にまとめます。


アクセサディスクリプタとデータディスクリプタ

getやsetという属性を持つディスクリプタの事をアクセサディスクリプタといいます

Getter/Setterを設定されたプロパティはこちらのディスクリプタとなります。

valueやwritableという属性を持つディスクリプタの事をデータディスクリプタといいます。

オブジェクトリテラルにおいて{kei:value}の形式で記述されたプロパティはこちらのディスクリプタになります。

言葉だけ覚えておくと資料読みの際等に役にたつかもしれません。


ディスクリプタのメタ属性

下記のモノで全てです。名前もわかり易いので一度使えば覚えられるレベルです。

writable属性をfalseにすることで定数を作る事もできますね。

名前
規定値
内容

configurable
false
trueの時、ディスクリプタ変更や、オブジェクトからプロパティ削除が可能

enumerable
false
trueの時、そのプロパティはkeysやfor...in...によるプロパティ列挙に現れる

value
undefined
プロパティの値。データディスクリプタのみ。

writable
false
trueの時、値を変更可能。データディスクリプタのみ。

get
undefined
Getter関数。アクセサディスクリプタのみ。

set
undefined
Setter関数。アクセサディスクリプタのみ。


Object.defineProperty

この関数はプロパティとディスクリプタを設定できます。


データディスクリプタを持つプロパティを生成

'use strict';

var man = {};
Object.defineProperty(man, 'age', {
value: 20,
writable: false,
});

man.age = 30; // TypeError: Cannot assign to read only property 'age' of #<Object>


writableをfalseにして上書きしようとすると通常は無視され、strictモードにおいてはTypeErrorとなります。

Getterのみを設定したときと同じですね。

今度はアクセサディスクリプタを持つプロパティを生成してみましょう。


アクセサディスクリプタを持つプロパティを生成

var man = {

_age: 0
};
Object.defineProperty(man, 'age', {
get: function() {
return this._age;
},
set: function(val) {
this._age = val;
},
});

defineSetter, defineGetterの時同様カプセル化が出来ておらず_age変数に直接アクセス出来てしまうのが相変わらず気になっていますが。。

このほかにも一度に複数のプロパティを設定できるObject.definePropertiesというメソッドもあります。


Object.create

Object.createは引数を二つとって新しいオブジェクトを返します。

第一引数はオブジェクトのprotoとなるオブジェクト。

第二引数はプロパティを列挙したオブジェクトです。プロパティの値はディスクリプタとなります。


createメソッド

var obj = Object.create({

name: 'foo'
}, {
age: {
value: 20,
writable: true
}
});
console.log(obj.name); // foo
console.log(obj.age); // 20


その他のディスクリプタ操作

その他にはディスクリプタ一括変更とその状態確認の処理があります。

名前
内容

preventExtensions
オブジェクトの拡張を禁止

isExtensible
オブジェクトの拡張が許可されているか

seal
オブジェクトの全プロパティの削除を禁止

isSealed
オブジェクトが封印(seal)されているか

freeze
オブジェクトの全プロパティに対して変更/削除を禁止する

isFrozen
オブジェクトが凍結(freeze)されているか

試しに一番強制力の強いfreezeメソッドを見てみましょう

var man = {

name: 'hosomichi',
age: 20
};
Object.freeze(man);
man.age = 30;
man.address = 'Tokyo';
console.log(man.age); // 20
console.log(man.address); // undefined

変更と拡張は無視されました。

strictモードだと下記のようなエラーが発生します。


  • 変更時

    TypeError: Cannot assign to read only property 'age' of #<Object>


  • 拡張時

    TypeError: Can't add property address, object is not extensible



まとめ

正直、いちいちディスクリプタを設定してられないと思うので

全部のオブジェクトにこれをやる必要は無いと思います。

しかし奔放すぎるJavaScriptのプロパティに対して結構な制御が可能となり、

下記のような場合は使えるのではないでしょうか。


  • 不変オブジェクトを造りたい!などオブジェクトの状態を保証したい場合

  • 定数を作りたい場合

  • 一部のプロパティをfor...in...などから隠蔽したい場合(enumerableがfalseの場合、for...in...ループでは登場しなくなりますが、Object.getOwnPropertyNames(object)を使用する事でプロパティ名の配列が取得出来ます。

今回は以上です。