はじめに
JSのクラスのメソッドは列挙属性がないので、for...in では列挙できません。結構不便。
getOwnPropertyNames では自分自身のプロパティは、列挙属性がないプロパティでも列挙できるので、それをprototypeをたどって、すべて列挙するようにできる関数を作りました。
次のページを大変参考にさせていただきました。
JavaScript Classのpropertyとmethodを列挙する - Qiita
https://qiita.com/BlueSilverCat/items/fc530e096a40beeb2a43
リンク先記事では、getAllMethodNames と getAllStaticMethodNames と getAllPropertyNames とがそれぞれ別関数になっていますが、
JSの場合結局は、Object.prototype が根っこにあり関数でもオブジェクトでも同じ扱いができるので、自分のコードでは統合しました。
JSではメソッドもプロパティの一部なので、メソッドなのかプロパティなのかは、そのプロパティ値が関数かどうかで判断するとよいです。
コードと動作確認
const propertyNames = (object) => {
const result = [];
let obj = object;
while (true) {
result.push(Object.getOwnPropertyNames(obj));
obj = Object.getPrototypeOf(obj);
if (obj === null) { break; }
}
return result;
}
class First {
constructor() {
this.dataFirst = "first";
}
funcFirst() { console.log("funcFirst"); }
static staticFuncFirst() { console.log("static funcFirst") }
}
class Second extends First {
constructor() {
super();
this.dataSecond = "second";
}
funcSecond() { console.log("funcSecond"); }
static staticFuncSecond() { console.log("static funcSecond") }
}
class Third extends Second {
constructor() {
super();
this.dataThird = "third";
}
funcThird() { console.log("funcThird"); }
static staticFuncThird() { console.log("static funcThird") }
}
let props;
props = propertyNames({});
console.log(props);
// [
// [],
// [
// 'constructor',
// '__defineGetter__',
// '__defineSetter__',
// 'hasOwnProperty',
// '__lookupGetter__',
// '__lookupSetter__',
// 'isPrototypeOf',
// 'propertyIsEnumerable',
// 'toString',
// 'valueOf',
// '__proto__',
// 'toLocaleString'
// ]
// ]
props = propertyNames(new First());
props.pop();
console.log(props);
// [ [ 'dataFirst' ], [ 'constructor', 'funcFirst' ] ]
props = propertyNames(new Second());
props.pop();
console.log(props);
// [
// [ 'dataFirst', 'dataSecond' ],
// [ 'constructor', 'funcSecond' ],
// [ 'constructor', 'funcFirst' ]
// ]
props = propertyNames(new Third());
props.pop();
console.log(props);
// [
// [ 'dataFirst', 'dataSecond', 'dataThird' ],
// [ 'constructor', 'funcThird' ],
// [ 'constructor', 'funcSecond' ],
// [ 'constructor', 'funcFirst' ]
// ]
props = propertyNames(() => {});
console.log(props);
// [
// [ 'length', 'name' ],
// [
// 'length', 'name',
// 'arguments', 'caller',
// 'constructor', 'apply',
// 'bind', 'call',
// 'toString'
// ],
// [
// 'constructor',
// '__defineGetter__',
// '__defineSetter__',
// 'hasOwnProperty',
// '__lookupGetter__',
// '__lookupSetter__',
// 'isPrototypeOf',
// 'propertyIsEnumerable',
// 'toString',
// 'valueOf',
// '__proto__',
// 'toLocaleString'
// ]
// ]
props = propertyNames(First);
props.pop(); props.pop();
console.log(props);
// [ [ 'length', 'prototype', 'staticFuncFirst', 'name' ] ]
props = propertyNames(Second);
props.pop(); props.pop();
console.log(props);
// [
// [ 'length', 'prototype', 'staticFuncSecond', 'name' ],
// [ 'length', 'prototype', 'staticFuncFirst', 'name' ]
// ]
props = propertyNames(Third);
props.pop(); props.pop();
console.log(props);
// [
// [ 'length', 'prototype', 'staticFuncThird', 'name' ],
// [ 'length', 'prototype', 'staticFuncSecond', 'name' ],
// [ 'length', 'prototype', 'staticFuncFirst', 'name' ]
// ]
説明
戻り値が文字列配列の配列になっています。
動作確認でも行っていますが、結果の重複になるので継承元の最後の、Object.prototype が不要な場合は出力前に pop をしています。Class関数を渡している場合には Function.prototype の情報も不要かと思ったので、2回の pop をしています。
戻り値の文字列配列の配列をユニーク文字列配列にする場合は、propertyNames の戻り値に対して flat して uniq するとよいです。
flatは、Array.prototype.flat() 使い
uniq は下記のようにすると手軽でよいと思います。
const a = ['A', 'B', 'B', 'A'];
console.log([...(new Set(a))]);
// ["A", "B"]