JavaScript のオブジェクトがある名前のプロパティをもっているのかどうか調べる方法や、オブジェクトがもっているプロパティの名前の一覧を取得する方法はいくつか種類があって、それぞれどういうものだったかちゃんと覚えてなかったのでまとめてみました。 基本的に data property か accessor property かというのは、プロパティの存在を調べる際やプロパティの名前の一覧を取得する際には関係ないのです。 プロパティの名前一覧を列挙するときには、プロパティが enumerable かどうかというのは関係します。 *1
ECMA-262 5.1 を参照してまとめました。
オブジェクトが指定のプロパティをもっているかどうかの検査
オブジェクトが、指定の名前のプロパティをもっているかどうか調べる方法。
in 演算子
var obj1 = { a: 200 }; var obj2 = Object.create( obj1 ); // obj2 はプロトタイプに obj1 をもつオブジェクト "a" in obj1; //=> true "a" in obj2; //=> true
in 演算子の右辺が示すオブジェクトに左辺が表す名前のプロパティが存在するかどうかを調べるもの。 自身にプロパティが定義されていなくても、プロトタイプに定義されていれば true を返してくれます。 プロパティの enumerable 属性が false であっても、true を返してくれます。 for-in ループの in と名前が同じで混同しやすいので注意 *2。
Object.prototype.hasOwnProperty メソッド
// obj1 と obj2 は上記のもの obj1.hasOwnProperty( "a" ); //=> true obj2.hasOwnProperty( "a" ); //=> false
in 演算子がプロトタイプチェインの上流も考慮する一方で、こちらはそのオブジェクト自身が指定された名前のプロパティをもっていなければ false を返します。 その他の細かな違いとしては、"test" in 100
は TypeError になる一方で、Object.prototype.hasOwnProperty.call( 100, "test" )
は例外を投げない、というのもあります。
オブジェクトがもっているプロパティの列挙
オブジェクトがもっているプロパティの一覧を得る方法。
for-in 文
Object.defineProperties( obj1, { "b": { value: 200, enumerable: true }, "c": { value: 300, enumerable: false } } ); for ( var n in obj1 ) { // ... } //=> "a", "b" がループに現れる for ( var n in obj2 ) { // ... } //=> "a", "b" がループに現れる
enumerable なプロパティを全てループで列挙してくれます。 プロトタイプチェインの中のオブジェクトのプロパティも含みます *3。 ループ中にプロパティ名が現れる順番は、ECMA-262 5.1 では指定されていません。 ループ中で削除されたプロパティは、(そのプロパティがまだ現れていないならば) ループ中に出現することはなく、また、ループ中に追加されたプロパティが必ずループ中に出現することは保証されません。
Object.keys メソッド
// obj1, obj2 は上の例と同じ Object.keys( obj1 ); //=> [ "a", "b" ] Object.keys( obj2 ); //=> []
自身がもっており (プロトタイプがもっていてもだめ)、かつ enumerable なプロパティの名前一覧が格納された配列 (Array オブジェクト) を返してくれます。
Object.getOwnPropertyNames メソッド
// obj1, obj2 は上の例と同じ Object.getOwnPropertyNames( obj1 ); //=> [ "a", "b", "c" ] Object.getOwnPropertyNames( obj2 ); //=> []
オブジェクト自身がもっているプロパティ (プロトタイプがもっていてもだめ) の名前一覧が格納された配列 (Array オブジェクト) を返してくれます。
あるオブジェクトが応答できる全てのプロパティ名の列挙
こうしてみると、あるオブジェクトが応答できる全てのプロパティ名 (オブジェクト自身か、そのプロトタイプチェイン中のオブジェクトがもっているプロパティ名一覧) を返すようなメソッドって ECMA-262 5.1 には含まれていないぽいですね。 そういうものを取得する方法としては、以下のようにプロトタイプを遡っていくという方法があります。 (この方法だと、同じ名前のプロパティをプロトタイプオブジェクトが持っていた場合に、結果に同じ名前が複数個含まれてしまうのでご注意ください。)
var propNames = []; var o = obj2; while ( o ) { propNames = propNames.concat( Object.getOwnPropertyNames( o ) ); o = Object.getPrototypeOf( o ); } propNames; //=> [ "a", "b", "c", "constructor", "toSource", "toString", "toLocaleString", "valueOf", "watch", // "unwatch", "hasOwnProperty", "isPrototypeOf", "propertyIsEnumerable", "__defineGetter__", // "__defineSetter__", "__lookupGetter__", "__lookupSetter__" ] // この結果は Firefox 12.0 の場合
*1:accessor property や enumerable については 「JavaScript に新しく導入された accessor property について」 とか 「ECMA-262 5th edition で導入された Object.defineProperty を使い、属性を指定してプロパティを定義する」 とかをご覧ください
*2:enumerable 属性が false のプロパティは、for in ループで列挙されない
*3:ただし、プロトタイプチェインの中の子が同名のプロパティをもっていない場合に限る