コドモン Product Team Blog

株式会社コドモンの開発チームで運営しているブログです。エンジニアやPdMメンバーが、プロダクトや技術やチームについて発信します!

JavaScriptのclassのtypeofはなぜfunctionなのか

こんにちは、プロダクト開発部の阿部です。 この記事はコドモン Advent Calendar 2022 15日目とJavaScript Advent Calendar 2022 16日目の記事として作成されました。

Calendar for コドモン | Advent Calendar 2022 - Qiita

Calendar for JavaScript | Advent Calendar 2022 - Qiita

まとめ

  • JavaScriptのclassをtypeof演算子で調べると"function"になる
  • これはJavaScriptが「プロトタイプベース言語」のため
  • classはfunctionを用いて再現しているだけで、中身は特殊な関数になっている

導入

JavaScriptにはclassという記法があり、これを使ってオブジェクトを生成できます。

class Human{
  constructor(name){
    this.name = name;
  }
  sayName(){
    console.log(this.name);
  }
}

const taro = new Human("taro");
console.log(typeof(Human)); // "function"

しかしtypeofしてみるとfunction型であることがわかりました。これはJavaScriptが「プロトタイプベース言語」であることに起因しています。今回はJavaScriptにおけるオブジェクト生成の流れについて説明し、なぜclassをtypeofすると"function"と出力されるかについて考えていきます。

プロトタイプベース言語

オブジェクト指向プログラミング言語において、オブジェクトを生成する方法は主に二つあります。

一つ目はオブジェクトの金型を作り、それをもとにオブジェクトを生成する方法です。このオブジェクトの金型を「クラス」、クラスをもとに生成されたオブジェクトのことを「インスタンス」といいます。この方法を採用しているプログラミング言語を「クラスベース言語」と呼びます。クラスベースは広く採用されており、有名な言語だとJavaやRuby、PHPがあります。

二つ目は既存のオブジェクトをもとにオブジェクトを生成する方法です。この元となるオブジェクトを「プロトタイプ」と呼びます。この方法を採用している言語を「プロトタイプベース言語」と呼び、JavaScriptはこの方法を採用しています。

JavaScriptにおけるオブジェクト生成の流れ

具体的なコードを少し読んでみます。下記のコードでは、human0オブジェクトをもとにしてhuman1オブジェクトを生成しています。この例だとhuman0がプロトタイプにあたります。また、human1をもとにhuman2を生成することができ、その場合はhuman1がプロトタイプになります。このように、オブジェクトのプロトタイプが鎖のようにつながっている仕組みをプロトタイプチェーンと呼びます。

// オブジェクトの生成
const human0 = {
  name:"サトシ"
}

// human0をもとにhuman1を生成、human1にlanguageプロパティを追加
const human1 = Object.create(human0);
human1.language = "Japanese"

console.log(human1.name) // サトシ
console.log(human1.language) // Japanese

const human2 = Object.create(human1);

human1.nameを参照したらhuman0で設定したプロパティであるnameが出力されました。これは該当プロパティをオブジェクトが持っていなかった場合、プロトタイプのオブジェクトのプロパティを参照しにいく仕様があるためです。これはプロトタイプチェーンの終端であるnullになるまで行います。

オブジェクトは[[Prototype]]というプライベートプロパティでプロトタイプへのリンクを保持しています。Object.getPrototypeOf関数で任意のオブジェクトのプロトタイプを確認できます。

const human0 = {
  name:"サトシ"
}
const human1 = Object.create(human0);
console.log(Object.getPrototypeOf(human1));// Object { name: "サトシ" }
Human.age = 3; // プロトタイプにプロパティを追加。
console.log(Object.getPrototypeOf(human1)) // Object { name: "サトシ", age: 3 }

コンストラクタ関数とnew演算子

次に、オブジェクトの生成時にプロパティを設定してみます。これで一般的な他言語のクラスを再現することができます。以下が簡単な手順です。

  1. コンストラクタ関数を宣言。必要なプロパティを設定する
  2. コンストラクタ関数のprototypeプロパティを設定。必要なメソッドを設定する
  3. new演算子を使用しコンストラクタ関数を実行、オブジェクトを生成。引数をここで渡す
// コンストラクタ関数の生成
function Human(name){
  this.name = name;
}
// prototypeプロパティを設定。これは[[Prototype]]ではないので注意。
Human.prototype.sayName = function(){
    console.log(this.name)
}
// インスタンス化
const taro = new Human("Taro");
console.log(taro.name) // Taro
taro.sayName() // Taro

コンストラクタ関数は普通の関数宣言と考えて問題ありません。細かく比較すると差異があります*1が、今回のスコープには含めません。

new演算子は実行すると暗黙的に下記を行います。

  1. 空のオブジェクトを生成し、thisを生成
  2. prototypeプロパティが指定されていたら[[Prototype]]に割り当てる
  3. 関数を実行(ここでthis.nameにnameが入る)
  4. 関数実行後にthisをreturnする

classがJavaScriptに実装される前は上記のようなfunctionを使った方法でクラスを再現し、オブジェクトを生成していました。ES6(ES2015)でclass構文が実装されてからは、functionを使ったオブジェクト生成はあまり見られなくなり、下記のようなクラスベース言語のような記法でオブジェクト生成ができるようになりました。

class Human{
  constructor(name){
    this.name = name;
  }
  sayName(){
    console.log(this.name);
  }
}

const taro = new Human("Taro");
taro.sayName(); // Taro

しかし、JavaScriptの言語仕様が変わったわけではなく、あくまでclassの立ち位置は特殊なfunctionであり、classという型が実装されたわけではありません。babelを用いてIE11でも使えるように変換してみると、正体がfunctionであることがわかります。

これがclassをtypeofした際にfunctionと出てくる理由になります。

まとめ

  • JavaScriptはプロトタイプベースのため、classという型は存在しない
  • classはfunctionを利用して実装しているため、実質的な中身はfunctionになっている
  • 上記理由から、classをtypeofするとfunctionになる

*1:関数の生成方法によってはコンストラクタ関数として扱えないものもあります。ECMAを確認してください。