プロパティの列挙と存在確認

《 初回公開:2022/11/17 , 最終更新:未 》

【 目次 】

プロパティの列挙

オブジェクトの全プロパティの列挙

ECMAScript 5 以降では、オブジェクトのプロパティをリストアップ/トラバース (横断走査) する言語特有の方法が 3 つあります。

  • for...in ループ この方法は、オブジェクトとそのプロトタイプチェーンにある列挙可能なプロパティをすべて横断します
  • Object.keys(o) このメソッドは、そのオブジェクト独自の (プロトタイプチェーンを除く) 列挙可能なすべてのプロパティ名 ("keys") を配列で返します
  • Object.getOwnPropertyNames(o) このメソッドは、そのオブジェクト独自のすべてのプロパティ名を (列挙可能かどうかに関わらず) 配列で返します

for…in loop

for…in loopオブジェクト自身が所有するプロパティだけではなく、prototypeをたどって継承元のプロパティも列挙される。
また、for…in loopenumerable属性がfalseのプロパティは列挙されないので注意。

配列の列挙には「for…in」では無く、数値のインデックスでの for ループ (か Array.prototype.forEach()for...of ループ) を使った方が良い。

Object.keysメソッド

Object.keysメソッドはオブジェクト自身が所有するプロパティ名の配列を返す。
for…in loopと異りprototypeをたどった継承元のプロパティは配列の要素に含まれない。

for…in loopと同じくenumerable属性がfalseのプロパティは含まれない

Object.getOwnPropertyNamesメソッド

Object.getOwnPropertyNamesメソッドもオブジェクト自身が所有するプロパティ名の配列を返す。
Object.keysメソッドと異なる点はenumerable属性がfalseのプロパティも含まれる事。

for…in loopとObject.keysメソッドObject.getOwnPropertyNamesとメソッドの動作の違いを検証

let o = { a: "aaa", f() { } };

// ここではoに設定されているメソッドも含め全てのプロパティが表示される

for (let prop in o)
    console.log(prop);  // aとfを出力

// fを再定義してenumerable属性をfalseに設定するとfは列挙されなくなる

Object.defineProperty(o, "f",
    { value() { }, configurable: true, enumerable: false, writable: true });

for (let prop in o)
    console.log(prop);  // aとfを出力


// for…in loopでは継承されている__proto__のプロパティnも余分に出力される。

o.__proto__ = { n: 99 };
for (let prop in o)
    console.log(prop);  // aとnを出力

// Object.keysでは継承されている__proto__のプロパティnは配列の要素に含まれない。

Object.keys(o)
for (let key of Object.keys(o))
    console.log(key);   // aしか出力されない

// Object.getOwnPropertyNamesではenumerable属性がfalseであっても配列の要素に含まれる。

for (let name of Object.getOwnPropertyNames(o))
    console.log(name);  // aとenumerable属性がfalseのfも出力

継承されているメソッドも含めすべてのプロパティを列挙する

上記のコードでオブジェクトoは組み込みのクラスObjectのインスタンスであってtoStringメソッド等のObject.prototypeに定義されている多くのメソッドが存在しているはずであるが、for…in loopではこれが列挙されていない。

Object.prototypeに定義されているtoStringメソッド等はenumerable属性がfalseに設定されていて列挙されない。

let o = new Object();
console.log(o.toString());

let d = Object.getOwnPropertyDescriptor(o.__proto__, 'toString');
console.log(d); // {writable: true, enumerable: false, configurable: true, value: ƒ}
console.log(d.enumerable);  // false

classキーワードでクラスを定義した場合の動作も検証。
まずはクラスを定義。

class MyCls {
    constructor() {
        this.iVar = "iVar";
    }
    iMethod() { }

    static cVar = "cVar";
    static cMethod() { }
}
let myObj = new MyCls();

クラスのメンバーのenumerable属性を調べてみると

let d;

d = Object.getOwnPropertyDescriptor(myObj, 'iVar');
console.log(d.enumerable);  // true
d = Object.getOwnPropertyDescriptor(myObj.__proto__, 'iMethod');
console.log(d.enumerable);  // false
d = Object.getOwnPropertyDescriptor(MyCls, 'cVar');
console.log(d.enumerable);  // true
d = Object.getOwnPropertyDescriptor(MyCls, 'cMethod');
console.log(d.enumerable);  // false

クラス定義においてインスタンスのメンバー変数とクラスのメンバー変数はenumerable属性がtrueに、
インスタンスとクラスのメソッドはfalseに設定されるようだ。

for…in loopで列挙されるプロパティは

for (let prop in myObj)
    console.log(prop);  // iVarのみを出力

for (let prop in MyCls)
    console.log(prop);  // cVarのみを出力

for (let name of Object.getOwnPropertyNames(myObj))
    console.log(name);  // iVarのみを出力

for (let name of Object.getOwnPropertyNames(myObj.__proto__))
    console.log(name);  // constructor,iMethodを出力

for (let name of Object.getOwnPropertyNames(MyCls))
    console.log(name);  // length,name,prototype,cMethod,cVarを出力

for…in loopでは列挙不可のプロパティは出てこないが、
getOwnPropertyNamesメソッドには出力される。
しかし継承されているプロパティは出力されない。
このため、プロトタイプチェインをたどってgetOwnPropertyNamesメソッドで列挙不可のプロパティも含めて列挙してみると

// myObjの継承の階層をたどると
let o = myObj;
let objName = "myObj";
while (true) {
    console.log(`${objName}: `);
    for (let propName of Object.getOwnPropertyNames(o))
        console.log("\t", propName, o.propertyIsEnumerable(propName));

    o = o.__proto__;
    if (o == null)
        break;
    objName = o.constructor.name + ".prototype";

継承されているプロパティも含めてmyObjのすべてのプロパティが列挙できる。
ここには、Object.prototypeのメンバーも含まれている。

実行結果

myObj: 
     iVar true
MyCls.prototype: 
     constructor false
     iMethod false
Object.prototype: 
     constructor false
     __defineGetter__ false
     __defineSetter__ false
     hasOwnProperty false
     __lookupGetter__ false
     __lookupSetter__ false
     isPrototypeOf false
     propertyIsEnumerable false
     toString false
     valueOf false
     __proto__ false
     toLocaleString false

同様に、クラスオブジェクトMyClsについても確認

// MyClsの継承の階層をたどると
let o = MyCls;
let objName = "MyCls";
while (true) {
    console.log(`${objName}: `);
    for (let propName of Object.getOwnPropertyNames(o))
        console.log("\t", propName, o.propertyIsEnumerable(propName));

    o = o.__proto__;
    if (o == null)
        break;
    if (o === Function.prototype) {
        objName = "Function.prototype"
    } else if (o === Object.prototype) {
        objName = "Object.prototype"
    } else {
        objName = o.constructor.name;
    }
}

クラスオブジェクトはFunctionクラスのインスタンスであって。

実行結果

MyCls: 
    length false
    name false
    prototype false
    cMethod false
    cVar true
Function.prototype: 
    length false
    name false
    arguments false
    caller false
    constructor false
    apply false
    bind false
    call false
    toString false
Object.prototype: 
    constructor false
    __defineGetter__ false
    __defineSetter__ false
    hasOwnProperty false
    __lookupGetter__ false
    __lookupSetter__ false
    isPrototypeOf false
    propertyIsEnumerable false
    toString false
    valueOf false
    __proto__ false
    toLocaleString false

ついでにFunction.prototypeやObject.prototypeではなく、
FunctionクラスとObjectクラス自身はどのようなプロパティを所有しているのかと言うと

for (let cls of [Function,Object]){
    console.log(`${cls.name}: `);
    for (let propName of Object.getOwnPropertyNames(cls))
        console.log("\t", propName, cls.propertyIsEnumerable(propName));
}

Functionクラスはlength,name,prototypeの基本的な3つのプロパティしかないようだ。
そしてFunction.prototypeやObject.prototypeと同様、
FunctionクラスやObjectクラスの所有するプロパティのenumerable属性もすべてfalseに。

実行結果

Function: 
     length false
     name false
     prototype false
Object: 
     length false
     name false
     prototype false
     assign false
     getOwnPropertyDescriptor false
     getOwnPropertyDescriptors false
     getOwnPropertyNames false
     getOwnPropertySymbols false
     is false
     preventExtensions false
     seal false
     create false
     defineProperties false
     defineProperty false
     freeze false
     getPrototypeOf false
     setPrototypeOf false
     isExtensible false
     isFrozen false
     isSealed false
     keys false
     entries false
     fromEntries false
     values false
     hasOwn false

プロパティの存在確認

hasOwnPropertyとhasOwn

hasOwnPropertyメソッドとhasOwnメソッドという似たような名前のメソッドが存在していて、違いがどこにあるのかというと。

Object.prototype.hasOwnPropertyメソッド

for…in loopを使ってオブジェクトのプロパティを列挙するとprototypeをたどって継承元のプロパティも列挙される。

let o = { a: "aaa" };
o.__proto__ = { b: "bbb" };

for (let prop in o)
    console.log(prop, o.hasOwnProperty(prop));  // aとfを出力

オブジェクト自身が所有するプロパティだけを列挙したい場合はhasOwnPropertyメソッドにより自身のプロパティかどうかをチェックをおこなう必要がある。

for (let prop in o)
    if (o.hasOwnProperty(prop))
        console.log(prop);  // aのみを出力

また、hasOwnPropertyメソッドはオブジェクトのプロパティの存在確認のためにも使える。

Object.hasOwnメソッド

ほとんどのオブジェクトはObjectクラスを継承しているのでObject.prototype.hasOwnPropertyメソッドが使える。
しかし、Objectクラスを継承しないオブジェクトも存在していてこの場合、Object.prototype.hasOwnPropertyメソッドが使えない。

オブジェクトの__proto__ にnullを代入しObjectクラスを継承しないようにすると

o.__proto__ = null;

前記のhasOwnPropertyメソッドの例では以下のようなエラーが発生する。

Uncaught TypeError: o.hasOwnProperty is not a function

そのような場合にはObject.hasOwnメソッドを使ってオブジェクト自身が所有するプロパティかどうかを確認する事になる。

for (let prop in o)
    if (Object.hasOwn(o, prop))
        console.log(prop);  // aのみを出力

in演算子

in演算子は単独でも使う事が出来てオブジェクトにプロパティが存在しているかを示す。

hasOwnPropertyメソッドやhasOwnPropertyメソッドとの違いは、in演算子ではfor…in loopと同様にプロトタイプチェインをさかのぼってプロパティを探す事。

let o = { a: "aaa" };
o.__proto__ = { b: "bbb" };

console.log(o.hasOwnProperty("a")); // trueを出力
console.log(o.hasOwnProperty("b")); // falseを出力
console.log(o.hasOwnProperty("c")); // falseを出力

console.log(Object.hasOwn(o, "a")); // trueを出力
console.log(Object.hasOwn(o, "b")); // falseを出力
console.log(Object.hasOwn(o, "c")); // falseを出力

console.log("a" in o);  // trueを出力
console.log("b" in o);  // trueを出力
console.log("c" in o);  // falseを出力

プロパティの値も含め列挙 - Object.valuesメソッドとObject.entriesメソッドとObject.keysメソッド

前述のObject.keysメソッドがプロパティ名が配列を返すのに対して、Object.valuesメソッドはプロパティの値を配列として返す。
同様に、Object.entriesメソッドはプロパティ名と値のペアの配列の配列を返す。

let o = { a: "aaa", n: 99 };
for (let key of Object.keys(o))
    console.log(key);   // aとnを出力
for (let value of Object.values(o))
    console.log(value); // aaaと99を出力
for (let entry of Object.entries(o))
    console.log(`[${entry[0]}, ${entry[1]}]`);  // [a, aaa]と[n, 99]を出力
ページのトップへ戻る