TypeScript でクラスをインスタンス化して、そのメソッド名をループで取得したかったが簡単にできなくてハマったので残しておく。
class MyClass { property = 24; method(a: number, b: number): number { return a + b; } } const obj = new MyClass(); console.log(Object.keys(obj)); for (const key in obj) { console.log(key); } /* 'property' // method がない! */
JavaScript ではプロトタイプ継承という仕組みがある。ザックリいうと、MyClass のインスタンス obj
は method
は持っていないのだが、obj.method
とアクセスしたときにチェーンされている prototype をさかのぼり実体の MyClass.prototype.method
が検索されアクセスできるという感じの機能。
継承とプロトタイプチェーン - JavaScript | MDN
for...in 文は、キーが文字列であるオブジェクトの列挙可能プロパティすべてに対して、継承された列挙可能プロパティも含めて反復処理を行います (Symbol がキーになったものは無視します)。 for...in - JavaScript | MDN
プロトタイプチェーンをふと思い出したので、これが原因か...?と思ったが key..in 文はプロトタイプチェーンをたどってくれるらしい。
「列挙可能プロパティ」というのが気になる Object.getOwnPropertyDescriptor
を使えば確認できるようなので見てみる。
console.log(Object.getOwnPropertyDescriptor(obj, "property")); // { value: 24, writable: true, enumerable: true, configurable: true } console.log(Object.getOwnPropertyDescriptor(obj, "method")); // --> undefined console.log(Object.getOwnPropertyDescriptor(MyClass.prototype, "method")); // 実体を見る必要がある /* { value: [Function: method], writable: true, enumerable: false, // 列挙不可能プロパティに設定されていた! configurable: true } */
obj.property
は列挙可能プロパティだが、obj.method
は列挙不可能なので、出てこなかったみたいだ。
結論
Object.getPrototypeOf()
でプロトタイプにアクセスし、そこから Object.getOwnPropertyNames()
で得られる。 Object.getOwnPropertyNames()
は列挙不可能プロパティも取得できるという特性を持っているので取得が可能になる。
class MyClass { property = 24; method(a: number, b: number): number { return a + b; } } const obj = new MyClass(); console.log(Object.getOwnPropertyNames(obj)); // [ 'property' ] console.log(Object.getOwnPropertyNames(Object.getPrototypeOf(obj))); // [ 'constructor', 'method' ]
継承している場合
また、何重にも継承をしていると、プロトタイプチェーンが長くなるので再帰的にアクセスする必要があったりする。これで親の親クラスにあるメソッド名 'methodOfSuperParent'
を得られた。色々関係ないメソッド名まで出てきてしまうので、もう少し工夫は必要そうではあるけれど。
class SuperParentClass { methodOfSuperParent(a: string, b: string): string { return `${a} * ${b}`; } } class ParentClass extends SuperParentClass { methodOfParent(a: string, b: string): string { return `${a} + ${b}`; } } class MyClass extends ParentClass { property = 24; method(a: number, b: number): number { return a + b; } } const obj = new MyClass(); let keys = Object.getOwnPropertyNames(obj); let prototype = Object.getPrototypeOf(obj); while (true) { keys = keys.concat(Object.getOwnPropertyNames(prototype)); prototype = Object.getPrototypeOf(prototype); if (!prototype) { break; } } console.log(keys); /* [ 'property', 'constructor', 'method', 'constructor', 'methodOfParent', 'constructor', 'methodOfSuperParent', 'constructor', '__defineGetter__', '__defineSetter__', 'hasOwnProperty', '__lookupGetter__', '__lookupSetter__', 'isPrototypeOf', 'propertyIsEnumerable', 'toString', 'valueOf', '__proto__', 'toLocaleString' ] */