【JavaScript】プロトタイプチェーンを含めたプロパティ名一覧を取得する

更新日:2023/02/27

JavaScriptのプロトタイプチェーンを継承と呼ぶこともあります。
これらのチェーン上のプロパティを含めてプロパティ名を列挙します。

 

考え方

プロパティ名を列挙するメソッドにObject.getOwnPropertyNames()とObject.keys()がありますが、これらはオブジェクト自身が持っているプロパティ名のみ取得できます。

つまり、プロトタイプチェーン上のプロパティ名は取得できません。

二つのメソッドについては、次のページで紹介しています。
【JavaScript】Object.getOwnPropertyNames()とObject.keys()の違い

そこで、プロトタイプチェーンを遡りながらプロパティ名を列挙していきます。
その際にプロパティ名が重複しないように、値を配列にセットします。

 

プロトタイプチェーンを含めてプロパティ名を取得する関数

プロトタイプチェーンを含めてプロパティ名を取得する関数は、次のようになります。

// obj: 対象のオブジェクト
// depth: 階層 obj自身は0
// isEnumerable: true → enumerable属性がfalseのものを対象にする
const getObjectPropertyNames = (obj,depth=0,isEnumerable=false) =>{
        // プロパティ名取得メソッドの選択
    const method = isEnumerable ? "getOwnPropertyNames" : "keys";
    
    return depth < 1 
            // 階層0のとき
        ? Object[method](obj)
            // 階層が1以上のとき
        : Array.from(   // Setオブジェクトを配列化するArray.from
            Array.from( {length:depth} ) // depth個の配列を作成するArray.from
                .reduce( (prevValue , e)=>{
                    if( prevValue.obj === null ) return prevValue; // チェーンが無い
                        // Setオブジェクトにプロパティ名をセット
                    Object[method]( prevValue.obj ).forEach( 
                        e=>prevValue.result.add( e ) 
                    );
                        // 次階層を取得
                    prevValue.obj = Object.getPrototypeOf( prevValue.obj );
                    return prevValue;
                },{ // reduceの初期値
                    obj : obj , result : new Set() 
                }
            ).result // Setオブジェクトを選択
        );
}

プロパティ名の取得は、Object.getOwnPropertyNames()とObject.keys()で行います。
この二つの違いは、次のページを読んでみてください。

取得したプロパティ名は、初期値で生成したSetオブジェクトにセットします。
Setオブジェクトは重複を排除してくれるので、今回の目的にピッタリです。

次にObject.getPrototypeOf()で取得したプロトタイプオブジェクトを、次回の対象にしてます。

これを、階層で指定した回数だけ繰り返します。

reduce()が終了したら、SetオブジェクトをArray.from()に渡して配列化して終了です。

 

動作確認

作成した関数を使用して、動作確認してみます。

const constructor = function(){
    this.a = 1;
    Object.defineProperty(this,"b",{
        value:2,
        enumerable:false
    });
}
constructor.prototype={c:5,d:6}

const testObj = new constructor();

console.log( getObjectPropNames(testObj,3) );
    // 結果:['a', 'c', 'd']
console.log( getObjectPropNames(testObj,3,true) );
    // ['a', 'b', 'c', 'd', 'constructor',
    //  '__defineGetter__', '__defineSetter__', 'hasOwnProperty',
    //  '__lookupGetter__', '__lookupSetter__', 'isPrototypeOf',
    //  'propertyIsEnumerable', 'toString', 'valueOf', '__proto__',
    //  'toLocaleString']

testObjは次のような階層になっています。

testObj {
    a : 1,
    b : 2, // enumerableがfalse
    [[プロトタイプチェーン]]:{
        c : 5,
        d : 6,
        [[プロトタイプチェーン]]:{ // Object.prototypeへの参照
            hasOwnProperty:メソッド,
            isPrototypeOf:メソッド
             ・・・・・
       }
    }
}

testObj.bObject.prototypeのプロパティはenumerablefalseなので、最初の結果には含まれていません。

更新日:2023/02/27

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

スポンサーリンク

記事の内容について

null

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

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

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

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

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

 

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