JavaScriptでインスタンスのクラス名を取得する方法

## はじめに

この記事は [PARONYM Advent Calendar 2021](https://qiita.com/advent-calendar/2021/paronym) の22日目の記事です。

## tl;dr

```javascript
/**
 * クラス名/関数名(Function)を取得する
 * @param {*} obj インスタンス。プリミティブ型でもいい
 * @returns {String}
 */
function getClass(obj) {
    if (obj === null) {
        return 'null';
    }
    if (obj === undefined) {
        return 'undefined';
    }
    return obj.constructor.name;
}
```


## JavaScriptのクラスとクラス名とは

ECMAScript5でJavaScriptに正式にclassが導入されました。classで宣言されたものがクラスです。
、、、なら単純ですがJavaScriptでclassの実態は今でもFunctionです。 `function hogehoge() {}` で宣言されるアレです。
クラスには `constructor()` というクラスの初期化メソッドが導入されました。なので全てのクラスは `constructor()` というメソッド/関数を持ちます。
なのでそのメソッドの名前を調べるのが1番確実です。


## 背景と解説

クラスはFunctionです。
Functionには `name` プロパティがあり参照できます。
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Function/name

```javascript
function ForExampleClass() {}
let name = ForExampleClass.name;
```

でもインスタンスからたどってもそんなものはありません。

```javascript
function ForExampleClass1() {}
let obj1 = new ForExampleClass1();
let name1_1 = ForExampleClass1.name; // これはOK
let name1_2 = obj1.name; // undefined
```

`name` を `this` に設定してもそれは望んでいるものではありません。

```javascript
function ForExampleClass2() {
    this.name = 'invalid';
}
let obj2 = new ForExampleClass2();
let name2_1 = ForExampleClass2.name; // これはOK
let name2_2 = obj2.name; // 望んでいるものではない
```

`prototype` を使って `name` を設定してもそれも望んでいるものではありません。

```javascript
function ForExampleClass3() {}
ForExampleClass3.prototype.name = 'invalid';
let obj3 = new ForExampleClass3();
let name3_1 = ForExampleClass3.name; // これはOK
let name3_2 = ForExampleClass3.prototype.name; // 望んでいるものではない
let name3_3 = obj3.name; // 望んでいるものではない
```

`this` に定義しても望んでいるものではありません。

```javascript
function ForExampleClass4() {
    this.name = 'invalid';
}
let obj4 = new ForExampleClass4();
ForExampleClass4.name = 'invalid';
ForExampleClass4.prototype.name = 'invalid';
let name4_1 = ForExampleClass4.name; // これはOK
let name4_2 = ForExampleClass4.prototype.name; // undefined
let name4_3 = obj4.name; // 望んでいるものではない
```

それならオブジェクトから文字列を求めるならメソッド名にそんな物があったよね、というのがこれです。おしい。

```javascript
function ForExampleClass5() {}
ForExampleClass5.prototype.toString = () => { return 'ForExampleClass5'; }
let obj5 = new ForExampleClass5();
let name5_1 = ForExampleClass5.name; // これはOK
let name5_2 = obj5.toString(); // これはOK
let name5_3 = Object.prototype.toString.call(obj5); // 望んでいるものではない
```

仕方ないのでclass機能を使うか〜となったのがこれですね。目的は達成できていますが定義が難しいですね。

```javascript
class ForExampleClass6 {
    get [Symbol.toStringTag]() { return 'ForExampleClass5'; }
}
let obj6 = new ForExampleClass5();
let name6_1 = ForExampleClass6.name; // これはOK
let name6_2 = Object.prototype.toString.call(obj6); // これはOK
```

最終的にclassを使うなら `constructor` がいるならそれでしょというのがこれです。

```javascript
class ForExampleClass7 {}
let obj7 = new ForExampleClass7();
let name7_1 = ForExampleClass7.name; // これはOK
let name7_2 = obj7.constructor.name; // これはOK
```

というわけでインスタンスからクラス名を取得する関数は最初の通りこうなりました。

```javascript
/**
 * クラス名/関数名(Function)を取得する
 * @param {*} obj インスタンス。プリミティブ型でもいい
 * @returns {String}
 */
function getClass(obj) {
    if (obj === null) {
        return 'null';
    }
    if (obj === undefined) {
        return 'undefined';
    }
    return obj.constructor.name;
}
function ForExampleClass8() {}
let obj8 = new ForExampleClass8();
let name8_1 = ForExampleClass8.name; // これはOK
let name8_2 = getClass(obj8); // これはOK
```