JavaScript で new をつけたりつけなかったりするやつ、なんなん?
なんなん?
JavaScript という言語、new Date()
や new Error()
みたいないかにも「インスタンス生成します!」という感じのコードもあれば、Boolean()
や Symbol()
みたいな「あれ、そこは new いらないんだ」みたいなコードもあり、よくわかりませんよね?
この new があるのとないのとでどう変わるんでしょうか。例を見ながら挙動を探っていきましょう。
例えば Date
new をつけて呼び出すものの代表格としては、Date でしょうか。
Date を new で呼び出すと、ご存じの通り Date のオブジェクトが返ってきます。
実は Date は new をつけなくても呼び出すことができます。試してみましょう。
一見すると new Date()
のパターンと変わらないように見えますが、よく見ると返ってきているのは文字列です。new をつけたときとそうでないときとで差があるようです。
例えば Error
Error オブジェクトもよく new をつけて呼び出す気がします。
何を今更という感じかもしれませんが、new Error()
が Error のオブジェクトを返すのを確認してみましょう。
巷のソースコードでは大体 new Error()
となっている気がしますが、実は new はつけなくても Error()
として実行できます。
なんと new Error()
と同じ結果が得られました。
例えば Boolean
逆に、普段 new をあまりつけないようなものも見てみましょう。
Boolean なんかが手頃でしょうか。Boolean()
を実行すると、引数の値を真偽値に変換してくれるので便利です。
例によって Boolean も new をつけて呼び出すことができます。
その結果を見てみましょう。
おや、真偽値でないものが返ってきてしまいました。
どうやら 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 に記載されています。
ここまでで、下記のことがわかりました。
- 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 に記載されています。
まずは 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 オブジェクト
(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 オブジェクト
(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 オブジェクト
(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