7つのプリミティブとそれ以外
JavaScript の値は大きく2つの世界に分かれます。片方は7種類の プリミティブ型 で、シンプルかつイミュータブルな値たち。もう片方は オブジェクト で、複合的・ミュータブル・呼び出し可能なものはすべてこちらに入ります。値レベルで見た JavaScript の型システムは、実はこれだけです。
プリミティブ型は次の7つです。
このリストに出てこないもの(配列、関数、Date、正規表現、素の {} など)はすべてオブジェクトです。実行時の型を調べるには typeof を使いますが、最後の行で有名な「あのバグ」に気づくはずです。typeof null は 1995 年以来ずっと 'object' を返し続けていて、これはもう直されることはありません。既存のコードが大量にこの挙動に依存しているからです。
プリミティブは「入れ物」ではなく「値そのもの」
一番しっくりくる捉え方はこれです。プリミティブは値「そのもの」だということ。数値の 3 は「3 という値を入れた箱」ではなく、ただの 3 です。3 を持つ 2 つの変数は、どこかにある共有の何かを指す 2 つのコピーではなく、同じ値そのものを持っているだけです。
プリミティブ型は値で比較され、オブジェクトは参照で比較されます。この違いこそが、後々「なんでこれが false になるの?」と悩む原因になります。特に配列やオブジェクトを === で比較するときに顕著です。
JavaScript のプリミティブ型はイミュータブル
プリミティブ型は変更できません。一見変更しているように見える操作も、実際には新しい値を生成しているだけです。
最初の呼び出しでは新しい文字列が作られていますが、戻り値を受け取っていないので捨てられてしまいます。一方、2 つ目は name に再代入しています。どちらの場合も、元の "ada" そのものが書き換わったわけではありません。そもそも書き換えることは不可能です。数値も同じで、x + 1 は新しい数値を作るだけで、x を変更しているわけではありません。
これこそが、文字列や数値に const を使っても本当に安全だと言える理由です。値自体が変わることはありませんし、const を使えば変数の再代入もブロックされます。
number と BigInt、なぜ 2 種類あるのか
JavaScript の number は 64 ビット浮動小数点数です。おかげで演算は高速ですが、上限もあります。整数として正確に扱えるのは Number.MAX_SAFE_INTEGER(2^53 - 1)までです。
この境界を超えると、整数同士がぶつかり始めます。どんなに大きくなっても厳密な値を保ちたいときに使うのが bigint です。書き方は末尾に n を付けるだけです。
bigint と number を混ぜて演算することはできません。混ぜられたら、わざわざ精度を拡張した意味がなくなってしまいますからね。bigint の出番は、データベースの ID、ナノ秒単位のタイムスタンプ、暗号処理などを扱うときです。普通の計算は number のままで十分です。
文字列もプリミティブ型
JavaScript の文字列はプリミティブ型であって、オブジェクトではありません。.length や .slice、.toUpperCase といったメソッドが使えるので紛らわしいのですが、れっきとしたプリミティブです:
内部的には、文字列のメソッドを呼び出すと、JavaScript はその場で String オブジェクトにラップしてメソッド呼び出しを成立させ、終わったらラッパーを捨てています。このラッパーのことを意識する必要はありません。ただ、文字列は豊富なメソッドを持ってはいるものの、値として振る舞う(イミュータブルで、値で比較される)という点だけ覚えておけば十分です。
シングルクォート、ダブルクォート、バッククォートはどれも同じ型を作ります。ただしバッククォートだけは、文字列への値の埋め込み(テンプレートリテラル)と複数行の記述が可能で、これについては次のドキュメントで扱います。
null と undefined の違い
「値がない」ことを表すプリミティブが 2 つあり、両者は使い分けが必要です。
undefined は、そもそも値が一度も代入されていないときに得られる値です。宣言だけして初期化していない変数、渡されなかった関数の引数、存在しないプロパティなどがこれにあたります。
null は「意図的に空っぽにしておくよ」と伝えたいときに、自分で明示的に書く値です。
だいたいの使い分けとしては、undefined は言語側が「ここには何もないよ」と言うときのもの、null はプログラマー自身が「何もない」と明示するときのもの、と覚えておけばOKです。どちらも falsy 扱いで、普通の値との等価比較は失敗します。詳しい話はそれぞれ別ページで改めて取り上げます。
Symbol 型:作った瞬間から必ずユニーク
プリミティブの中でいちばん出番が少ないのが symbol 型です。Symbol は作るたびに必ずユニークな値になります。たとえ同じ説明文字列から作ったとしても、別物として扱われます。
シンボルはオブジェクトのキーとして便利で、既存のキーとぶつかる心配がありません。ライブラリがあなたのオブジェクトにメタデータを付けたいとき、シンボルを使えば他のコードに上書きされる心配がないというわけです。のちほどイテレータや Symbol.iterator のようなwell-knownシンボルを扱う場面で、また登場します。
実行時に型を調べる方法
たいていのケースは typeof で事足ります。ただし、いくつかクセがあるので押さえておきましょう。
null の判定は value === null のように直接比較します。配列なら Array.isArray(value) を使いましょう。「プリミティブ型かどうか」を一発で判定する組み込み関数はありませんが、次のようなイディオムがよく使われます。
プリミティブとオブジェクトの違い:代入でハマるポイント
先へ進む前に、もう一つ見ておきたいポイントがあります。プリミティブは「値そのもの」、オブジェクトは「参照」なので、代入したときの挙動がまったく違ってくるんです。
プリミティブ型では b = a は値のコピーになります。一方、オブジェクトの場合 y = x は参照のコピーで、両方の名前が同じ実体を指します。片方を通して変更すると、もう片方にも影響します。JavaScript で「え、なんで勝手に変わってるの?」という不具合を生む最大の原因がこれです。
このページのまとめ
- プリミティブは7種類:
string、number、bigint、boolean、null、undefined、symbol。これ以外は全部オブジェクトです。 - プリミティブはイミュータブルで値で比較され、オブジェクトはミュータブルで参照で比較されます。
typeofは実行時に型を返してくれますが、覚えておきたいクセが2つあります:typeof null === "object"とtypeof function(){} // "function"です。numberは64ビット浮動小数点数で、整数として安全に扱える上限があります。その上限を超える正確な整数が必要なときのためにbigintが用意されています。
次回: 文字列とテンプレートリテラル
文字列は一番よく触るプリミティブです。そして、テンプレートリテラル(バッククォートで囲む文字列)を使えば、変数の埋め込みや複数行のテキスト、タグ付きテンプレートまで楽に書けます。次のページで扱います。
よくある質問
JavaScriptのプリミティブ型はいくつある?
全部で7つです。string、number、bigint、boolean、null、undefined、symbol の7種類。これ以外の配列、関数、Date、オブジェクトリテラルなどはすべてオブジェクト型になります。実行時に型を調べたいときは typeof を使いますが、歴史的な経緯で typeof null だけは 'object' を返すので要注意です。
プリミティブとオブジェクトは何が違うの?
プリミティブは値そのもので比較される、イミュータブルな値です。3 と 3 はどちらも同じ 3。一方、オブジェクトは参照で比較されるミュータブルな存在なので、見た目が同じ {} でも別物扱いになります。変数に代入するときも、プリミティブなら値がコピーされますが、オブジェクトの場合は同じ実体を指す参照がコピーされる、という違いがあります。
JavaScriptのプリミティブは本当に変更できないの?
はい、プリミティブはイミュータブルです。'hello'.toUpperCase() は新しい文字列を返すだけで、元の 'hello' は一切変わりません。x = x + 1 のような再代入も、変数が指す先を別のプリミティブに差し替えているだけで、元の値をいじっているわけではないんです。だからこそ const name = 'Ada' と宣言しても、name を元にした新しい文字列を作ることは問題なくできます。
なぜ typeof null は 'object' を返すの?
1995年の初期実装のバグが原因です。ただ、すでに多くのコードがこの挙動に依存していたため、今も修正されずに残っています。null を判定したいときは value === null のように === で比較してください。undefined なら value === undefined か typeof value === 'undefined' を使うのが定番です。