🆕

JavaScript で new をつけたりつけなかったりするやつ、なんなん?

2023/12/25に公開

なんなん?

JavaScript という言語、new Date()new Error() みたいないかにも「インスタンス生成します!」という感じのコードもあれば、Boolean()Symbol() みたいな「あれ、そこは new いらないんだ」みたいなコードもあり、よくわかりませんよね?

この new があるのとないのとでどう変わるんでしょうか。例を見ながら挙動を探っていきましょう。

例えば Date

new をつけて呼び出すものの代表格としては、Date でしょうか。
Date を new で呼び出すと、ご存じの通り Date のオブジェクトが返ってきます。

new Date(2023, 11, 25) の 実行結果

実は Date は new をつけなくても呼び出すことができます。試してみましょう。

Date(2023, 11, 25) の 実行結果

一見すると new Date() のパターンと変わらないように見えますが、よく見ると返ってきているのは文字列です。new をつけたときとそうでないときとで差があるようです。

例えば Error

Error オブジェクトもよく new をつけて呼び出す気がします。
何を今更という感じかもしれませんが、new Error() が Error のオブジェクトを返すのを確認してみましょう。

new Error('エラーだょ') の 実行結果

巷のソースコードでは大体 new Error() となっている気がしますが、実は new はつけなくても Error() として実行できます。

Error('エラーだょ') の 実行結果

なんと new Error() と同じ結果が得られました。

例えば Boolean

逆に、普段 new をあまりつけないようなものも見てみましょう。
Boolean なんかが手頃でしょうか。Boolean() を実行すると、引数の値を真偽値に変換してくれるので便利です。

Boolean(42) の 実行結果

例によって Boolean も new をつけて呼び出すことができます。
その結果を見てみましょう。

new Boolean(42) の 実行結果

おや、真偽値でないものが返ってきてしまいました。
どうやら new Boolean() の返り値は何かしらのオブジェクトのようです。

このオブジェクトに生えている valueOf というメソッドを実行すると、期待する真偽値が得られることが確認できました。

なんか全体的に挙動に統一感なくない?

オブジェクト間での挙動の統一感のなさが気になるところです。この挙動を解明するために情報を整理しましょう。

built-in object

Error や Date、Boolean といったような、JavaScript に組み込まれていてどこでも参照できるオブジェクトは 「built-in object」と呼ばれるものです。

built-in object には、String や Math といった馴染み深いものもあれば、DataView や Reflect といったややマイナーなものまで含まれます。

built-in object の中には、自身を関数として実行できるインターフェースを持っているものがあり、それらは「function object」 と呼ばれています。Boolean オブジェクトは Boolean() として実行できるので function object ということですね。

そして、この function object の中に new 演算子を使えるものがあります。new 演算子に対するインターフェースをもつオブジェクトは、「constructor」と呼ばれています。Boolean は new Boolean() できるので、function object であり、constructor でもあるということになります。

この整理は ECMAScript に記載されています。
https://262.ecma-international.org/#table-additional-essential-internal-methods-of-function-objects

ここまでで、下記のことがわかりました。

  • Error や Date、Boolean などは built-in object と呼ばれるものである
  • built-in object の中には関数呼び出しができるものがある (= function object)
  • function object の中には new 演算子で呼び出せるものがある (= constructor)

先程例示した Error、Date、Boolean はどれも function object であり、constructor でもあるという整理になります。

余談
  • built-in object の中には関数呼び出しができるものがある (= function object)
  • function object の中には new 演算子で呼び出せるものがある (= constructor)

と記述しましたが、逆に言うと

  • built-in object の中には関数呼び出しができないものがある
  • function object の中には new 演算子で呼び出せないものがある

ということでもあります。
前者は Math オブジェクト、後者は Symbol オブジェクトなんかが該当します。

new と関数呼び出し

ここで本題に戻るわけですが、built-in objcet を new で呼び出したときと関数で呼び出したときとではどういった違いがあるのでしょうか。

その答えも当然 ECMAScript に記載されています。
https://262.ecma-international.org/#sec-objects

まずは new から。

Objects are created by using constructors in new expressions; for example, new Date(2009, 11) creates a new Date object.

オブジェクトは new が使われた constructor によって作られる、と書いてあります。逆に言うと、new を使うと constructor によってオブジェクトが作られるということですね。ここで作られるオブジェクトはいわゆるインスタンスと呼ばれるものです。

new Date() なら Date オブジェクトが、new Boolean() なら Boolean オブジェクトが得られるということです。クラスベースのオブジェクト指向言語を知っている人なら想像しやすい挙動でしょう。

次に、new を使わない形での関数呼び出しです。

Invoking a constructor without using new has consequences that depend on the constructor. For example, Date() produces a string representation of the current date and time rather than an object.

何が返ってくるかは「ものによる」とのことです。

つまり、new Error() new Date() new Boolean() がそれぞれのオブジェクト (インスタンス) を返すのは自明な仕様である一方、関数呼び出しの Error() Date() Boolean() が何を返すかはそれぞれのオブジェクトの仕様によるということです。

じゃあ関数呼び出しで何が返ってくるかってどこみればわかるの?

MDN にも補足的に書いてありますが、原典として ECMAScript を参照してみましょう。

Error オブジェクト

https://262.ecma-international.org/#sec-error-constructor

(The Error constructor) creates and initializes a new Error object when called as a function rather than as a constructor. Thus the function call Error(…) is equivalent to the object creation expression new Error(…) with the same arguments.

new Error()Error() は同じだよ、と書いてあります。どちらも新しい Error オブジェクトを返すということですね。

Date オブジェクト

https://262.ecma-international.org/#sec-date-constructor

(The Date constructor) creates and initializes a new Date when called as a constructor.
(The Date constructor) returns a String representing the current time (UTC) when called as a function rather than as a constructor.

Error オブジェクトとは違い、new のパターンと関数呼び出しのパターンとで記載が分かれています。

new Date() で呼び出された場合は Date オブジェクトを返し、Date() で呼び出された場合は String を返すと書かれています。

Boolean オブジェクト

https://262.ecma-international.org/#sec-boolean-objects

(The Boolean constructor) creates and initializes a new Boolean object when called as a constructor.
(The Boolean constructor) performs a type conversion when called as a function rather than as a constructor.

こちらも記載が分かれています。

new Boolean() で呼び出された場合は Boolean オブジェクトを返し、Boolean() で呼び出された場合は type conversion が実行されるとのことです。type conversion というのは、この場面では引数を真偽値に変換する処理のことを指しています。

個別の事例を出されても結局よくわからないので、全部教えてくれない?

個別に見ていっても結局どういったルールや傾向があるのかわからないので、全 built-in object (Global Object を除く) を表形式で眺めてみましょう。
「new」の列は new Something() を実行したとき、「関数呼び出し」の列は Something() を実行したときの返り値です。

Fundamental objects

オブジェクト名 new 関数呼び出し
Object インスタンス type conversion の実行結果
Function インスタンス インスタンス
Boolean インスタンス type conversion の実行結果
Symbol - シンボルの値
Error インスタンス インスタンス

Numbers and dates

オブジェクト名 new 関数呼び出し
Number インスタンス type conversion の実行結果
BigInt インスタンス type conversion の実行結果
Math - -
Date インスタンス String の値

Text Processing

オブジェクト名 new 関数呼び出し
String インスタンス type conversion の実行結果
RegExp インスタンス インスタンス

Indexed Collections

オブジェクト名 new 関数呼び出し
Array インスタンス インスタンス
TypedArray (Int8Array など) インスタンス -

Map Objects

オブジェクト名 new 関数呼び出し
Map インスタンス -
Set インスタンス -
WeakMap インスタンス -
WeakSet インスタンス -

Structured Data

オブジェクト名 new 関数呼び出し
ArrayBuffer インスタンス -
SharedArrayBuffer インスタンス -
DataView インスタンス -
Atomics - -
JSON - -

Managing Memory

オブジェクト名 new 関数呼び出し
WeakRef インスタンス -
FinalizationRegistry インスタンス -

Control Abstraction Objects

オブジェクト名 new 関数呼び出し
Iterator - -
Promise インスタンス -
GeneratorFunction インスタンス インスタンス
Generator - -
AsyncGeneratorFunction インスタンス インスタンス
AsyncFunction インスタンス インスタンス

Reflection

オブジェクト名 new 関数呼び出し
Reflect - -
Proxy インスタンス -

所感

こうして眺めてみると、

  • 関数呼び出しで String の値を返す Date と、関数呼び出しのみのインターフェースを持っている Symbol はかなり例外的
  • プリミティブ型のデータに紐づくオブジェクト (Boolean、Number、BigInt、Stirng) と Object は、関数呼び出しで type conversion が実行される
  • それ以外は下記の3パターン
    • new も関数呼び出しもできない
    • new はできるが関数呼び出しはできない
    • new も関数呼び出しもでき、関数呼び出しの場合はインスタンスを生成する

といった整理になりそうです。

これで明日から迷わず new をつけたりつけなかったりできますね。

この記事は SmartHR Advent Calendar 2023 シリーズ2の25日目の記事でした。
それではハッピーメリークリスマス。

Discussion