[JavaScript] Class の継承元をたどって property を全て列挙する

## はじめに

JSのクラスのメソッドは列挙属性がないので、for...in では列挙できません。結構不便。

getOwnPropertyNames では自分自身のプロパティは、列挙属性がないプロパティでも列挙できるので、それをprototypeをたどって、すべて列挙するようにできる関数を作りました。

次のページを大変参考にさせていただきました。

> JavaScript Classのpropertyとmethodを列挙する - Qiita
> https://qiita.com/BlueSilverCat/items/fc530e096a40beeb2a43

リンク先記事では、getAllMethodNames と getAllStaticMethodNames と getAllPropertyNames とがそれぞれ別関数になっていますが、

JSの場合結局は、Object.prototype が根っこにあり関数でもオブジェクトでも同じ扱いができるので、自分のコードでは統合しました。

JSではメソッドもプロパティの一部なので、メソッドなのかプロパティなのかは、そのプロパティ値が関数かどうかで判断するとよいです。

## コードと動作確認

```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 は下記のようにすると手軽でよいと思います。

```js
const a = ['A', 'B', 'B', 'A'];
console.log([...(new Set(a))]);
// ["A", "B"]
```