【JavaScript】オブジェクトのプロパティを列挙するいくつかの方法

更新日:2023/04/17

JavaScriptでオブジェクトのプロパティ名を列挙する方法がいくつかあります。
それぞれ特徴がありますが、使い方によっては想定した結果にならないこともあります。

そこでプロパティを列挙する方法と、注意しておくべき点をお伝えします。

 

Object.keys()を使う

Object.keys()は、次の二つの条件に一致するプロパティの名前を配列にセットして返します。

  • オブジェクトが直接所持している、つまりプロパティチェーン上ではない
  • 列挙可能である

forEach()で列挙

取得した配列のforEach()を呼び出すことで、プロパティ名を列挙できます。
オブジェクトに列挙不可のプロパティを定義して、Object.keys()の結果に含まれるかどうか確認してみます。

const obj = { a:1 , b:2 };
  // 列挙不可のプロパティを定義
Object.defineProperty( obj , "c" ,{ 
    enumerable:false, // 実際はデフォルトで列挙不可
    value:3
} );

Object.keys( obj ).forEach( e=>{
  console.log( e );
   // 結果: 
   //  a
   //  b
});

列挙不可のプロパティcは、配列にセットされませんでした。

class構文は注意が必要

class構文で定義したメソッドは、プロトタイプチェーンに配置されます。
そのため、Object.keys()の結果に含まないので列挙できません。

const A = class{
  propA = "A";
  funcA(){}; // プロトタイプチェーンに配置される
};

const a = new A();

Object.keys( a ).forEach( e=>{
  console.log( e );
    // 結果
    //  propA 
});

 

for...inを使う

for...inは、列挙可能なプロパティをプロパティチェーンを追って列挙できます。

コンストラクターで確認

コンストラクターでオブジェクトを生成して、プロパティチェーン上のプロパティが列挙できるか確認してみます。

const constructorA = function(){
  this.prop1 = 1;
    // 列挙不可のプロパティを定義
  Object.defineProperty( this , "prop2" ,{ 
    enumerable:false, // 実際はデフォルトで列挙不可
    value:2
  } );
};
constructorA.prototype.funcA = function(){};
  // 列挙不可のプロパティを定義
Object.defineProperty( constructorA.prototype , "funcB" ,{ 
    enumerable:false, // 実際はデフォルトで列挙不可
    value:function(){}
} );

const cA = new constructorA();

for( const prop in cA ){
  console.log( prop );
   // 結果: 
   //  prop1
   //  funcA
}

プロトタイプチェーン上のプロパティも列挙されましたが、列挙不可に設定したものは列挙されませんでした。

オブジェクトのプロトタイプチェーンは最終的に、Objectオブジェクトにたどり着きます。
このオブジェクトのプロパティが列挙されないのは、列挙不可になっているからです。

class構文は列挙不可

オブジェクトを作成する方法の一つに、class構文があります。
この構文で定義したメソッドは列挙不可になります。
つまり、for...inで列挙できません。

const A = class{
  propA = "A";
  funcA(){}; // プロトタイプチェーンに配置される
};

const a = new A();

for( const prop in a ){
  console.log( prop );
   // 結果: 
   //  propA
}

class構文で定義したメソッドは、Object.keys()でもfor...inでも列挙できないのです。

 

列挙不可プロパティを列挙する

プロトタイプチェーンを含めて、列挙不可のプロパティの名前を配列にセットする関数を作成しました。

// obj .. 対象のオブジェクト
// ignoreObjectProto .. Objectオブジェクトを除外するかどうか
const getForInAllPropertys = (obj,ignoreObjectProto=true)=>{
  const loopCheck = new WeakSet(); // ループ回避用
  const propNames = new Set(); // プロパティ名セット用

  const isObjectProto = ignoreObjectProto 
          ? ( proto )=> proto === Object.prototype
          : ()=>false;

  do{
    loopCheck.add( obj );
    Object.getOwnPropertyNames(obj).forEach( e=>propNames.add( e ) );
    obj = Object.getPrototypeOf( obj );
  }while( obj !== null &&  !isObjectProto(obj) &&  !loopCheck.has( obj ));
  
  return [...propNames];
}; 

Object.getOwnPropertyNames()は、列挙可能かどうかに関係なく、所持するプロパティの名前を取得します。

取得した名前はSetオブジェクトにセットします。
Setオブジェクトは同じ値をセットできないので、重複を削除できます。

全てセットしたら、Object.getPrototypeOf()でプロトタイプチェーンの参照先オブジェクトを取得します。
このとき取得したオブジェクトが、親オブジェクトだと無限ループします。
その確認のために、WeakSet()を使用します。

ホントに無限ループするのか気になって確認してみました。
【JavaScript】プロトタイプチェーンは無限ループするのか

あとは、プロパティ名をSetオブジェクトにセットの繰り返しです。

関数を実行してみます。

const A = class{
  propA = "A";
  funcA(){};
};

const a = new A();
getForInAllPropertys( a ).forEach(e=>{
  console.log( e );
   // 結果:
   // propA
   // constructor
   // funcA
});

プロトタイプチェーンを含めて、列挙不可のプロパティを取得できました。

なお次のようにすると、hasOwnProperty()などのObjectオブジェクトのプロパティも取得できます。

getForInAllPropertys( a , false ).forEach(e=>{
  console.log( e );
   // 結果:
   // propA
   // constructor
   // funcA
   // toString
   // toLocaleString
   // valueOf
   // hasOwnProperty
   // ・・・以下略
});

更新日:2023/04/17

書いた人(管理人):けーちゃん

スポンサーリンク

記事の内容について

null

こんにちはけーちゃんです。
説明するのって難しいですね。

「なんか言ってることおかしくない?」
たぶん、こんなご意見あると思います。

裏付けを取りながら記事を作成していますが、僕の勘違いだったり、そもそも情報源の内容が間違えていたりで、正確でないことが多いと思います。
そんなときは、ご意見もらえたら嬉しいです。

掲載コードについては事前に動作確認をしていますが、貼り付け後に体裁を整えるなどをした結果動作しないものになっていることがあります。
生暖かい視線でスルーするか、ご指摘ください。

ご意見、ご指摘はこちら。
https://note.affi-sapo-sv.com/info.php

 

このサイトは、リンクフリーです。大歓迎です。