こんにちは、ほそ道です。
今回は前回の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
プロパティのディスクリプタを取得してみましょう。
man.age = 100;
var descriptor = Object.getOwnPropertyDescriptor(man, 'age');
console.log(descriptor);
// 出力
// {get: [Function: age],
// set: [Function: age],
// enumerable: true,
// configurable: true }
今度はフツーにオブジェクトリテラルで宣言したプロパティのディスクリプタを取得してみましょう。
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__となるオブジェクト。
第二引数はプロパティを列挙したオブジェクトです。プロパティの値はディスクリプタとなります。
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)
を使用する事でプロパティ名の配列が取得出来ます。
今回は以上です。