型変換には2種類ある
JavaScriptは型に対してゆるい言語です。演算子に「想定外」の型の値が渡されても、エラーを投げずに裏で型を変換してくれます。これが**型変換(coercion)**で、大きく分けて2種類あります。
- 明示的型変換 — 自分で書いて変換するパターン:
Number("42")、String(99)、Boolean(value)など。 - 暗黙の型変換 — 変換コードを書いていないのに、演算子が勝手に変換するパターン:
"5" - 2、"" == 0、if (value)など。
どちらも内部的には同じ変換ルールが走っています。違いは「自分で変換を指示したか」「言語側が勝手にやったか」だけ。JavaScriptで「なんでこうなるの?」となる挙動 — "5" + 1 === "51"、[] == false、null == undefined など — のほとんどは、この暗黙の型変換に足をすくわれたケースです。
イメージとしてはこうです。Number(...) や String(...) と自分で書いているときは、何に変換したいかが自分でわかっています。でも書いていないときは、各演算子が勝手に「こう変換すべきだろう」と判断します。バグが潜むのはまさにこの“勝手な判断”の部分です。
変換先となる3つの型
型変換(type coercion)の行き先は、必ず string・number・boolean という3つのプリミティブ型のいずれかです(厳密には bigint という4つ目もありますが、他の型から自動で変換されることはありません)。変換ルールの細かい話はすべて、「その演算子がどの型を狙っているか」で決まってきます。
文字列への型変換は寛容で、どんな値にも文字列表現があります。ただし注意したいのは、オブジェクトが "[object Object]" という役に立たない形に変換されてしまう点です。オブジェクトを文字列と連結してログに出す("user: " + user のような書き方)と、期待した内容がほぼ表示されないのはこれが原因です。こういう場合は JSON.stringify を使うか、テンプレートリテラルで必要なフィールドだけ展開しましょう。
数値への型変換
数値への変換はもっと厳しめです。文字列は「数値として読める形」になっていないと NaN になります。
括弧で補足した挙動は、特にハマりやすいので覚えておく価値があります。
- 空文字列やスペースだけの文字列は
NaNではなくnullになる。 nullはnullになるのに、undefinedはNaNになる。- 空の配列は
null、要素が1つだけの配列は その要素 が変換され、要素が複数ある配列はNaNになる。
余計な文字が混じった文字列から数値だけを取り出したいときは、Number ではなく parseInt や parseFloat を使いましょう。
parseInt を使うときは、必ず基数(10)を第2引数に渡しましょう。これを省くと、"0x" で始まる文字列が16進数として解釈されてしまい、意図しない結果になりがちです。
Boolean への型変換
Boolean への変換は、3種類ある型変換の中で一番シンプルです。ごく一部の値だけが false に変換され、それ以外はすべて true になります。
falsy な値は次のとおりです。
falsenull,-0,0n""(空文字列)nullundefinedNaN
以上です。これ以外の値はすべて truthy として扱われます。"false" や "0"、さらには [] や {} まで truthy になる点には注意してください。
空配列や空オブジェクトの挙動は、Python から来た人がよくハマるポイントです。Python だと空のコレクションは falsy ですが、JavaScript では truthy 扱いになります。「この配列は空か?」を判定したいなら、arr.length === 0 のように明示的にチェックしましょう。
Boolean への変換(Boolean 変換)は、真偽値が期待される場所に値が現れるたびに発生します。具体的には if (...)、while (...)、三項演算子 ? :、そして論理演算子 &&、||、! です。
+ 演算子だけは特別扱い
ほとんどの算術演算子はオペランドを数値に変換しますが、+ だけは別物です。どちらか一方 でも文字列があれば、+ は文字列連結として動作します。それ以外の場合は数値としての加算になります。
評価は左から右へと進みます。1 + 2 + "3" の場合、まず 1 + 2 = 3 が計算され、その次に 3 + "3" = "33" になります。一方 "1" + 2 + 3 は最初から文字列モードに入っているので、そのまま文字列として "123"、"123" と連結されていきます。
こういう挙動があるので、+ で文字列を組み立てるやり方は壊れやすいんです。テンプレートリテラルを使えば、この問題は起きません。
テンプレートリテラルは count + 1 を独立した数値式として評価してから、その結果を文字列に埋め込みます。暗黙の型変換に振り回されることはありません。
明示的な型変換を使おう
型を変換する必要があるときは、はっきりとコードで示しましょう。文字数は少し増えますが、後からコードを読む人にとって曖昧さが一切なくなります。
真偽値も同じです。!!value でも動きますしよく見かける書き方ですが、Boolean(value) の方が何をしているのかが一目でわかります。
まっとうな方針としては、アプリケーションのロジックでは明示的な変換を使い、+x や !!x のような省略形は簡潔さが効いてくる場面、しかも意図が文脈から明らかなときに限って使うのがおすすめです。
== 演算子は型変換にベッタリ頼っている
型変換絡みで一番ハマりやすいのが等価演算子です。== は比較の前に型変換をしますが、=== はしません。
上の例で出てくる true は、どれも多段階の型変換チェーンを経た結果で、正直なところ大半の開発者はそらで説明できません。ここが問題です。たまたま動いているコードは、いずれ壊れるコードです。等価演算子の詳しいルールは次のページで扱いますが、今の段階で押さえておきたいのは次の一点。基本は === を使うこと。== を使うのは x == null のように null と undefined の両方をまとめて判定したい特定のイディオムに限る、ということです。
実例で整理する
型変換が役に立つ場面と、逆に落とし穴になる場面を、ひとつの例にまとめて見てみましょう。
2つ目の呼び出しに注目してください。Number("") は NaN ではなく null を返すので、parsePrice("") の結果も null になります。関数を使う側としては、これは意図した挙動ではないはずです。空文字列を弾きたいなら、明示的にチェックを入れましょう。
どの値が null になり、どの値が NaN になるのか。これを押さえておくと、後から地味にハマるバグを未然に防げます。
この章のまとめ
- 型変換は、演算子によって文字列・数値・真偽値のいずれかに変換される。
+は片方でも文字列があれば文字列連結になる。それ以外の算術演算子は必ず数値に変換される。- falsy な値は決まったリストしかない。丸暗記してしまうのがおすすめ。それ以外はすべて truthy で、
[]や{}も truthy に含まれる。 Number("")はnull、Number([])もnull、Number(null)もnull。ただしNumber(undefined)はNaN。この違いは実際のバグの温床になりやすい。- 暗黙の型変換に頼ったトリックより、
Number(x)、String(x)、Boolean(x)といった明示的型変換を使おう。あとで読み返す自分が感謝するはず。
次回:等価演算子
比較における型変換のクセは、すべて == の中に詰まっています。次のページでは == と ===、そして Object.is の違い、いまでも == が役に立つ唯一のイディオム、そしてなぜ多くの Linter がデフォルトで == に警告を出すのかを見ていきます。
よくある質問
JavaScriptの型変換(Type Coercion)とは何ですか?
型変換とは、JavaScriptが値をある型から別の型へ自動的に変換することです。数値が文字列になったり、文字列が数値になったり、あらゆる値が真偽値になったりします。+や==、ifといった演算子が想定外の型を受け取ったときに暗黙的に発生し、自分でNumber(x)・String(x)・Boolean(x)を呼ぶと明示的に行われます。
暗黙的な変換と明示的な変換の違いは?
明示的変換は、自分の意思で変換関数を呼ぶこと。たとえばNumber("42")、String(99)、Boolean(value)などです。一方、暗黙的変換は演算子が裏で勝手に型を変えてしまうもの。"5" - 2は3になるのに、"5" + 2は"52"になる、というアレですね。明示的なコードは読みやすく挙動も予測しやすい一方、暗黙的な変換は「なんでここNaNになるの…?」系バグの温床になりがちです。
JavaScriptで文字列を数値に変換するには?
厳密に変換したいならNumber("42")を使います(数値として読めない文字列はNaNになります)。"42px"のように数値の後ろに余計な文字が付いている場合はparseInt("42px", 10)やparseFloat("3.14em")が便利です。単項+演算子(+"42")もNumber()と同じ挙動をしますが、コードをざっと読んだときに見落としやすいので注意してください。
なぜ[] + []は空文字列になるのですか?
[] + []は空文字列になるのですか?配列同士を+で足しても、意味のある数値演算がありません。そのためJavaScriptは両方を文字列に変換します。配列は要素をカンマで連結した文字列になり、空配列は""になります。結果として[] + []は"" + ""と評価され、""になるというわけです。小ネタとしては面白いですが、プリミティブ以外に+を使うべきではない、という良い教訓でもあります。