プロパティの列挙と存在確認
《 初回公開:2022/11/17 , 最終更新:未 》
【 目次 】
プロパティの列挙
オブジェクトの全プロパティの列挙
ECMAScript 5 以降では、オブジェクトのプロパティをリストアップ/トラバース (横断走査) する言語特有の方法が 3 つあります。
- for...in ループ この方法は、オブジェクトとそのプロトタイプチェーンにある列挙可能なプロパティをすべて横断します
- Object.keys(o) このメソッドは、そのオブジェクト独自の (プロトタイプチェーンを除く) 列挙可能なすべてのプロパティ名 ("keys") を配列で返します
- Object.getOwnPropertyNames(o) このメソッドは、そのオブジェクト独自のすべてのプロパティ名を (列挙可能かどうかに関わらず) 配列で返します
for…in loop
for…in loop
はオブジェクト自身が所有するプロパティだけではなく、prototypeをたどって継承元のプロパティも列挙される。
また、for…in loop
はenumerable属性が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]を出力