{ } で囲まれたコードの解釈はコンソールにより異なる

最近、JavaScriptのオブジェクト初期化子(リテラル)は高機能になって来ました。
記法を確認するため、Node.jsや各種ブラウザなどのコンソール(repl)にコードを入力してみることも多いのではないでしょうか。
しかし、オブジェクト初期化子のつもりで {x: 1} などと入力しても、オブジェクトが生成されないコンソールがあります。

例えばFirefoxですが、

Firefoxのコンソールにて
// 入力行
{x: 1}

// 表示行
1

となります。
Node.jsやChromeのコンソールならオブジェクトが生成されて、

Node.jsのコンソールにて
// 入力行
{x: 1}

// 表示行
{ x: 1 }

のように表示されるのに、なぜFirefoxでは 1 が表示されるのでしょうか。

{x: 1} の構文木

まず、何もない所に {x: 1} とだけ書いた場合、JavaScriptでは本来どのように構文解析されるのか確認します。
esprima.org に構文解析して構文木を見せてくれるページがありましたので、入力してみました

AST抜粋
"type": "BlockStatement",
"body": [
    {
        "type": "LabeledStatement",
        "label": {
            "type": "Identifier",
            "name": "x"
        },
        "body": {
            "type": "ExpressionStatement",
            "expression": {
                "type": "Literal",
                "value": 1,
                "raw": "1"
            }
        }
    }
]

BlockStatementブロック文)の中に x というラベルが付いた LabeledStatementラベル付き文)があり、その中身が 1 という式になっています。

Firefoxのコンソールは素直な動作をしている

つまり、何もない場所でコードが { } で囲われていると、それはブロック文と見なされるのが通常であって、Firefoxのコンソールはそのままの素直な動作をしているだけのようです。
コンソールが表示するのは最後に評価された値ですから、ブロック文とラベル付き文の中身の式、1 が評価され、そのまま表示されます。

{ } で囲われたコードがオブジェクト初期化子と見なされるのは、値が期待される場所に置かれている場合です。
例えば、代入式の右辺( foo = {x: 1} )、かっこの中( ({x: 1}) )など、たくさん考えられます。
Firefoxでもこういったコードをコンソールに入力すると、{x: 1} というオブジェクトが生成されて表示されます。

Node.jsのコンソールには利便性のための仕掛けがある

上を踏まえると、Node.jsやChromeのコンソールの動作の方が不思議になります。
{x: 1} と入力するだけでオブジェクト初期化子と見なされるのはなぜでしょうか。
これは実は簡単な仕掛けで、利便性のためにインチキをしているのです。

Node.jsの中身(repl.js)を見てみると、入力コードが { } で囲われている場合に ( ) でラップする処理があります。

repl.js(175行目付近)
if (/^\s*\{/.test(code) && /\}\s*$/.test(code)) {
      // It's confusing for `{ a : 1 }` to be interpreted as a block
      // statement rather than an object literal.  So, we first try
      // to wrap it in parentheses, so that it will be interpreted as
      // an expression.
      code = `(${code.trim()})\n`;
      wrappedCmd = true;
}

このラップされたコードをパースしてみて駄目であれば、オブジェクト初期化子ではないのかな、ということで、もう一回、今度はラップ無しでそのまま試すようです。

repl.js(201行目付近)
} catch (e) {
  debug('parse error %j', code, e);
  if (wrappedCmd) {
    wrappedCmd = false;
    // unwrap and try again
    code = input;
    wrappedErr = e;
    continue;
  }

ブロック文か、オブジェクト初期化子か、解釈をコントロールするには

{ } で囲まれたコードの解釈がコンソールにより異なる仕組みがだいたい解りました。
最後に、この解釈をコンソールによらず明示的に選択する方法を考えてみました。

オブジェクト初期化子として解釈させる方法:

// かっこで囲む
({x: 1})

// カンマ演算子の右に置く
1,{x: 1}

ブロック文として解釈させる方法:

// セミコロンを先頭か末尾に付ける
{x: 1};

この記事のライセンス

クリエイティブ・コモンズ・ライセンス
この記事はCC BY 4.0(クリエイティブ・コモンズ 表示 4.0 国際 ライセンス)の元で公開します。