🕌

JavaScriptでObjectに空のStringを足すと0になる!?……わけではなかった

2024/01/24に公開

JavaScriptのコードを読んでいるとき、ある場所の挙動が気になって、ブラウザのJavaScriptコンソールに {} + '' や {foo: 'bar'} + '' を評価させたところ、0 が返ってくるということに気付きました。

{} + "" // => 0
{foo: "bar"} + "" // => 0

JavaScriptコンソールの表示

JavaScriptでは演算子によって暗黙的にデータの型が変換されて、プログラマーの予想とは違った結果になることがあります。以下の例は、StringとNumberが + 演算子によって式になっている場合にはNumberがStringに変換され、- のときは StringがNumberに変換されています

"1" + 2 // => "12"
"2" - 1 // => 1

しかしこの、「ObjectにStringを足したらNumberになった」というのは、JavaScriptに長年親しんでいる身として、予想外の結果でした。

代入したりカッコに入れたりすると結果が異なる

不思議に思っていろいろ試しているうちに、{}{foo: 'bar'} を、変数に代入してから + '' すると、結果が変わり、 "[object Object]" が返ってくることに気付きました。このほうが個人的には納得感のある挙動です("[object Object]"という文字列が出てきて良かった記憶は思い当たるものがないですが)。

const hoge = {};
hoge + ""; // => "[object Object]"
const fuga = {foo: "bar"};
hoge + ""; // => "[object Object]"

あるいはカッコに入れても、同じことが起こります。

({}) + ""  // => "[object Object]"
({foo: "bar"}) + "" // => "[object Object]"

代入やカッコを使ったときのコンソールの表示

ASTをみてみよう

この不思議な現象を調査するために、AST(Abstract Syntax Tree: 抽象構文木)の状態を見てみることにしました。ASTはソースコードを構文解析した結果をツリー構造にしたもので、AST Explorerを使うと簡単に見ることができます。

({}) + "" のAST

({}) + "" のASTをみると、ひとつの ExpressionStatement となっているのがわかります。ExpressionStatement の leftは ObjectExpression 、 operator は + 、rightは Literal となっていて、たしかに Object と String の足し算になっています。これなら確かに"[object Object]" が返ってくるでしょう。

AST Explorerのスクリーンショット

{} + ""のAST

しかし、{} + "" のASTをみると、BlockStatementExpressionStatement で構成されています。

AST Explorerのスクリーンショット

つまり、{} は空のブロック であり、その次に + "" つまり単項演算子として「正負」の「正」の符号 + をつけた空文字列があるという、2つの文によって構成されるプログラムとなっていたのでした。空のブロックは何も処理がなく、そして + "" は暗黙的に Numberの 0 に変換され、それがコンソールに表示されていたのでした。

ブロックは、 iffor の制御によく使いますが、そういえば iffor がなくても、 constlet のスコープを作るために使うことができるのでした。

{foo: "bar"} + "" のAST

では、 {foo: "bar"} + "" の場合はどうなんでしょうか。実は {} については「空のブロックでは?」という予想のもとにASTを見て正解だったのですが、 {foo: "bar"} は中身もあるし、Objectっぽく見えて、とても不思議に感じながら試していました

AST Explorerのスクリーンショット

やはり今回も、{} の部分は BlockStatementで、 + "" の部分が ExpressionStatement となり、その結果が 0 であるということのようです。そして、 foo: "bar" の部分は何かというと、LabeledStatement つまり 「foo という ラベルのついた、 `"bar" という文」ということになっていました。

ラベルは、continuebreak でループを抜けるときに使うという知識は持っていましたが、JavaScriptで使えるかどうかなんて意識したことがありませんでした……。

おわりに

当初は「ObjectにStringを足すとNumberになる??なんで???」と思って調べてみたものの、ASTをみてみるとObjectですらなくブロック構文であったということがわかりました。そして、忘れかけていたり、使ったことのない構文が突如として現われたりした、という楽しい出来事でした。この経験が、これから役に立つことがあるかは、よくわかんないです……。

Discussion