JavaScript における 1 + '1' と 1 - '1' の違い
JavaScript の型変換において、ちょっと面白いと思うことがあったので記事に残す。
問題
JavaScript (ECMAScript) で、以下を評価する。
const a = 1 + '1'; const b = 1 - '1';
すると、a には string 型の '11'
が、b には number 型の 0
が、それぞれ入る(Safari の開発者ツールと Node.js で試したが、結果は同じ)。
a // '11' b // 0
直感的に、(a, b) は (2, 0) か ('11', '') になるだろうと思っていたので、ちょっと意外な結果となった。
調査
このように評価される理由を探るため、ECMAScript の仕様書を見てみる。
+
と -
の仕様は、'Additive Operators' の節に載っている(以下のリンクを参照)。
https://tc39.es/ecma262/#sec-additive-operators
仕様を辿っていくと、+
と -
のどちらの場合も EvaluateStringOrNumericBinaryExpression という関数が呼ばれ、
さらにその中で ApplyStringOrNumericBinaryOperator という関数が呼ばれることがわかる。
この関数の仕様は、以下のリンクの先に書いてある。
https://tc39.es/ecma262/#sec-applystringornumericbinaryoperator
興味深いのが、+
とそれ以外とで処理が異なる点である。仕様の一部分を以下に引用する。
ApplyStringOrNumericBinaryOperator ( lval, opText, rval ) (省略) 1. If opText is +, then a. Let lprim be ? ToPrimitive(lval). b. Let rprim be ? ToPrimitive(rval). c. If lprim is a String or rprim is a String, then i. Let lstr be ? ToString(lprim). ii. Let rstr be ? ToString(rprim). iii. Return the string-concatenation of lstr and rstr. 2. NOTE: At this point, it must be a numeric operation. 3. Let lnum be ? ToNumeric(lval). 4. Let rnum be ? ToNumeric(rval). (以下省略)
つまり、演算子が +
でかつ左か右のどちらかの値が文字列であるときは、文字列結合を行なっている。
1 + '1'
の評価では右の値が文字列だったため、'11'
が得られたのである。
一方、1 - '1'
の評価ではこの特別な処理が行われないため、数値に揃えられて引き算が行われ、0
が得られた。
結び
この設計はちょっと分かりづらいが、自然だと思う。 個人的にも、プラスで文字列結合する発想はあるが、マイナスで文字列の除去をする発想はあまり思い浮かばない。