おい、調子はどうだい?
最近、コードレビューをしていて「またこれか……」と頭を抱えたポイントがあるんだ。それが今回取り上げる加算演算子(`+`)の闇、つまり「文字列連結」と「数値加算」の優先順位と暗黙の型変換だ。
中級への階段を登り始めたエンジニアによくあるのが、「APIから返ってきた数値(文字列型になってるやつ)と、カウンターの数値を足し合わせたら、なぜか変な文字列が爆誕した」っていうバグにハマる現象。
今回は、JavaScriptのエンジンが裏側で一体どんな魔法(あるいは悪夢)を使っているのか、そして現場で二度とバグを踏まないための防衛策を、シニアの俺が徹底的に叩き込んでやる。ついてきな。
—
1. 現場を崩壊させる「1 + ‘1’ = ’11’」の正体
まずは基本のおさらいだ。JavaScriptを書く上で、以下のコードの結果を瞬時に答えられないようじゃ、今日の夜は帰れないと思ったほうがいい。
console.log(1 + ‘1’); // 答えは?
……そう、答えは `”11″`(文字列)だ。数学の常識で生きている人間からすると「えっ?」ってなる瞬間だよな。なぜ数値の `1` と文字列の `’1’` を足したのに、数値の `2` にならず、文字列が連結されてしまうのか。
ここに、JavaScriptの仕様(ECMAScript Specification)における加算演算子(Addition Operator `+`)の宿命がある。
実は、JavaScriptの `+` 演算子は、「数値の加算」と「文字列の連結」の2つの役割を兼務させられているんだ。これが諸悪の根源。
演算子が評価される際、JavaScriptエンジンは「どっちの処理をすべきか」を判定するために、オペランド(演算子の左右にある値)の型をチェックする。
暗黙の型変換のルール:文字列がすべてを支配する
JavaScriptの仕様書(ToPrimitive抽象操作など)をめくると、加算演算子の挙動はざっくりこう決まっている。
1. 左右どちらかのオペランドが文字列(String)である場合、あるいは、オブジェクトをプリミティブ値に変換した結果が文字列になる場合、JavaScriptはもう片方のオペランドも強制的に文字列に変換する。
2. そして、それらを結合(コンカネーション)する。
つまり、`1 + ‘1’` の場合、左側の数値 `1` が「お、相方が文字列なら俺もそっち側に合わせるぜ」とばかりに、裏側で `”1″` という文字列に強制変換(暗黙の型変換)され、最終的に `”1″` と `”1″` が連結されて `”11″` が出来上がるというわけだ。文字列の引力、恐るべし。
—
2. 実行順位(結合性)がもたらすカオスな現場
単体ならまだしも、これが複数連なると、現場のエンジニアを絶望させるカオスが生まれる。
次のコードを見てくれ。左から右へ評価されるときの、エンジンの挙動を追ってみよう。
const result = 1 + 2 + ‘3’;
console.log(result); // 何が出力される?
正解は、`”33″` だ。
「えっ、`1 + 2` で `3` になって、それに `’3’` を足すんだから `6` じゃね?」と思ったそこの君。甘い。甘すぎるぞ。
加算演算子の結合性は「左から右(Left-to-Right)」だ。ここがめちゃくちゃ重要。
1. まず、左側の `1 + 2` が評価される。
- 両方とも数値なので、これは純粋な「数値加算」として処理される。
- 結果は `3`(数値型)。
2. 次に、その結果(`3`)と、右側の `’3’`(文字列型)が `+` で結ばれる。
- 式としては `3 + ‘3’` になる。
- ここで「片方が文字列」のルールが発動し、数値の `3` が文字列の `”3″` に変換される。
- 最終的な結果は `”3″ + “3”` で、`”33″` になる。
じゃあ、順番を変えたらどうなる?
const result2 = ‘1’ + 2 + 3;
console.log(result2); // 出力は?
これは `”123″` になる。
なぜなら、最初に `’1′ + 2` が評価された時点で、全体の優先順位が「文字列連結モード」に固定されてしまうからだ。`’12’ + 3` になり、最終的に `”123″` が出来上がる。
これが、実務でフォームの入力値(文字列)と計算用の数値をごちゃ混ぜにして管理したときに起こる、原因不明のバグの正体だ。
—
3. 実務で使える!事故を防ぐベストプラクティス
こういう仕様がある以上、「じゃあ仕様に気をつけながらコード書きます」なんて甘いこと言ってちゃプロ失格だ。人間はミスをする生き物だからこそ、バグが入り込む余地をコードレベルで排除する(防御的プログラミング)のがシニアのやり方だ。
現場ですぐに使える、安全なコードの書き方を伝授しよう。
パターンA:入力を受け取ったら「一刻も早く」明示的に型変換する(Number / String)
APIレスポンスやDOMの `value`(これらは100%文字列だ)を受け取ったら、計算に使う前に明示的に数値へキャスト(型変換)する癖をつけろ。これだけで事故の9割は防げる。
// 【NGな例】フォームの入力値をそのまま足してしまう
function calculateTotalNG(priceInput, taxInput) {
return priceInput + taxInput; // “100” + “8” = “1008” という大惨事に
}
// 【OKな例】受け取った瞬間にNumberで包む、あるいは単項プラス演算子を使う
function calculateTotalOK(priceInput, taxInput) {
// Number() を使うか、数値化を保証する処理を入れる
const price = Number(priceInput);
const tax = Number(taxInput);
// これなら安全に数値加算ができる
return price + tax;
}
ちなみに、単項プラス演算子(`+priceInput`)を使うのもスマートでよく使われるテクニックだ。
パターンB:テンプレートリテラルで「意図した文字列結合」を明示する
逆に、数値を綺麗にメッセージなどの文字列に埋め込みたい場合は、古い `+` 演算子を使った文字列連結をやめて、テンプレートリテラル(バッククォート “ ` “)を使え。
const count = 5;
// 【NGな例】プラス演算子でメッセージを作る
const messageNG = ‘現在のアイテム数は ‘ + count + ‘ 個です。’;
// 【OKな例】テンプレートリテラルで意図を明確にする
const messageOK = `現在のアイテム数は ${count} 個です。`;
テンプレートリテラルの中であれば、内部で自動的に文字列化されることが保証されているし、何よりコードの可読性が段違いに上がる。無駄な `+` をコードから駆逐できるんだ。
—
まとめ:仕様を嘆くな、武器にしろ
JavaScriptの暗黙の型変換は、初期の言語設計の名残であり、しばしばクソ仕様扱いされる。だけど、プロのフロントエンドエンジニアなら、その仕様の裏側にあるルール(今回で言えば「文字列が絡むと加算は文字列連結に化ける」という優先順位)を完全に理解し、コントローラブルに使いこなせなきゃいけない。
明日からのコードレビューでは、他人のコードの `+` 演算子を見る目が変わるはずだ。「あ、こいつ、入力値の型を信頼しきってやがるな……」って気づけるようになる。
チームのコードの品質を担保するのは、いつだってこういう基礎の徹底だ。しっかり手を動かして、体に覚え込ませてくれ。それじゃ、次の現場へ戻るとしようか。

コメント