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 が得られた。

結び

この設計はちょっと分かりづらいが、自然だと思う。 個人的にも、プラスで文字列結合する発想はあるが、マイナスで文字列の除去をする発想はあまり思い浮かばない。