無駄と文化

実用的ブログ

JavaScript で関数とクラスを見分ける

JavaScript においては typeof や constructor を参照するだけでは関数と class を見分けることができません。

// ただの関数
function myFunction() {}

// class
class MyClass {}

// typeof では見分けられない
typeof myFunction;  // => 'function'
typeof MyClass;     // => 'function'

// constructor でも見分けられない
myFunction.constructor;  // => Function
MyClass.constructor;     // => Function

関数と class を見分けるには toString() すると良いです。

myFunction.toString();  // => 'function myFunction() {}'
MyClass.toString();     // => 'class MyClass {}'

class の場合、toString() した結果が 'class ' から始まります。

もし関数と class を判別する関数を書くとしたらこのようになります。

function isFunction(obj) {
  return typeof obj === 'function' && !obj.toString().match(/^class /);
}

function isClass(obj) {
  return typeof obj === 'function' && obj.toString().match(/^class /);
}

isFunction の方の条件を obj.toString().match(/^function /) にしていないのは、アロー関数を toString() した場合に結果が 'function ' から始まらないからです。

 

なぜこんなことになっている?

なぜ typeof や constructor だけでは関数と class が見分けられないのでしょうか。
それは class は本質的に関数と同じものだからです。

一昔前 [いつ?] まで JavaScript には class 構文が無くて、人々は function 構文を使って class 相当のものを書いていました。

例えばこのように、

class Rectangle {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }

  area() {
    return this.height * this.width;
  }
}

// ↓↓↓ かつてはこう書いていた

function Rectangle(height, width) {
  this.height = height;
  this.width = width;
}

Rectangle.prototype.area = function() {
  return this.height * this.width;
};

メソッドを定義するときに .prototype を参照しています。このあたりの構文が JavaScript がプロトタイプベース言語であることを思い出させてくれますね。 *1

このように私たちが class 構文で定義しているものは関数でもあるので、二つを見分けるのには特殊なテクニックが必要になってしまっています。

 

区別する必要はない?

この件を ChatGPT に聞いてみると「JavaScript において class は関数と同等なので二つを区別する必要はありません」と言われました。本当でしょうか?

もう一度 function 構文を用いて定義した Rectangle クラスのコードを引用します。

// コンストラクタ
function Rectangle(height, width) {
  this.height = height;
  this.width = width;
}

// area メソッドの定義
Rectangle.prototype.area = function() {
  return this.height * this.width;
};

コンストラクタの中では return で値を返すことをしていません。そのため Rectangle(2, 3) のように new を付けずに関数として Rectangle を呼び出すと返り値は undefined になってしまいます。

というわけでやはり関数と class の区別は必要ですね。おのれ ChatGPT 。

 

まとめ

function 構文を用いた class 定義、久しぶりに書いたのでとても懐かしい気持ちになりました。

 

 

私からは以上です。

*1:実際には現代もこのあたりの事情はまったく変わっていなくて、 class 構文は単なる function 構文のシンタックスシュガーです