乱暴に言うと、JavaScriptのnew演算子は、関数を新しいオブジェクトを生成するコンストラクターに変える特別なキーワードである。

コンストラクターってなんやねん

コンストラクターは、newキーワード付きで呼び出される、functionキーワードで定義された関数である。この関数は、中身が空っぽであってもコンストラクターとして扱われ、エラーは出ない。

ただし、同じく関数を定義するものであっても、アロー関数式はコンストラクターとしては扱われない。これはアロー関数が自身のthis(関数やメソッドの呼び出し元の参照)を持たないためである。

js
function C1() {}
const C2 = function () {};
const C3 = () => {};
console.dir(new C1());
console.dir(new C2());
console.dir(new C3());
/**
 * 結果:
 * C1
 * C2
 * Uncaught TypeError: C3 is not a constructor
 */

また、JavaScriptにはclass構文がある。classキーワードで定義されたクラスオブジェクトもまた、コンストラクターとして機能する。

js
class C{}
console.dir(new C);
/**
 * 結果:
 * C
 * /

ちなみにclass構文の中で、constructor(){}メソッドを定義することもできるが、必須ではない。らしい。

訳がわからない。

なぜfunctionclassも、constructor(){}が不要なのだろう。

内部メソッド[[Call]]

JavaScriptでは、オブジェクトの構造は内部メソッド(’Internal methods’)と呼ばれるアルゴリズムによって指定される。なお、メソッドとは言うものの、これらは概念的なもので実際には存在せず、実装方法は各ベンダーによって異なる。

JavaScriptの呼び出し可能なオブジェクトは、[[Call]]という内部メソッド(=アルゴリズム)を持つ。functionキーワードやアロー関数式によって定義されたオブジェクトはいずれもこの[[Call]]内部メソッドを持っている。

js
function f1() {}
const f2 = function () {};
const f3 = () => {};
const f4 = {};
f1();
f2();
f3();
f4();
/* 結果:
 * Uncaught TypeError: f4 is not a function
 */

普通のオブジェクトは[[Call]]を持たないため、呼び出そうとするとTypeErrorとなる。

内部メソッド[[Construct]]

呼び出し可能な、つまり[[Call]]内部メソッドを持つ関数オブジェクトのうち、functionキーワードを使って定義するものは、[[Construct]]という内部メソッドを持っている(=アロー関数式で定義した関数は、[[Construct]]を持っていない)。

このメソッドを持つオブジェクトをnewキーワードを使って呼び出すと、新たなオブジェクトを生成して返す。これがJavaScriptのコンストラクターの正体である(たぶん)。

また、関数オブジェクトと違い、クラスは呼び出し出しできない。

js
class C {}
C();
/**
 * 結果:
 * Uncaught TypeError: Class constructor C cannot be invoked without 'new'
 */

かと思いきや、その型は関数オブジェクトだったりする。仕様を見ても、クラス構文を評価する際は最終的に関数を返すようになっている。……と言うと自分で読み解いたみたいたが、実際にはChatGPTとClaudeに解読してもらった。

js
class C {}
console.log(typeof C);
/**
 * 結果:
 * function
 */

じつはクラスオブジェクトも[[Call]]内部メソッドを持っている。new X()みたいな形で呼び出せるのだから、考えてみれば当然である。しかし関数と違い、クラスオブジェクトをnewキーワードなしで呼び出すと、型エラーとなる。

クラスオブジェクトは、変則的な関数オブジェクトであると言える。

constructor()メソッド

class構文の中では、constructor()メソッドを1つだけ定義できる(2つ以上定義すると構文エラーとなる)。constructor()メソッドが定義されている場合、デフォルトの[[Construct]]内部メソッドの振る舞いをそこに付与する。

これによって、オブジェクトの初期化処理を柔軟にカスタムできるようになっている。

constructor()メソッドの返り値

constructor()メソッドでプリミティブ値以外の値を返すと、そのクラスオブジェクトのインスタンスではなくその値が返される。

なんで?

わからないが、Claudeが言うには柔軟性のためと、newキーワードなしで呼び出す関数の挙動と揃えるため、らしい。前者はともかく、後者はよくわからない。

newキーワードとは

迷宮に迷い込んでしまって、この記事を書き始めた趣旨を忘れていた。

newキーワードは、以下の3つの関数オブジェクト(あるいはその関数様オブジェクト)のいずれかの呼び出しとともに使うことで、新たなオブジェクトを生成して返すためのものである。

  • function f(){}
  • const f = function(){}
  • class{}

で、newキーワードともに使われる場合に、これらの呼び出し可能なオブジェクトをコンストラクターと呼ぶ。

終わり。

参考資料