25
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

自由ライセンスで記事を公開してみようAdvent Calendar 2017

Day 3

{x: 1} は何と見なされるか —— JavaScriptコンソールによる違い

Last updated at Posted at 2017-12-03

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

最近、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 国際 ライセンス)の元で公開します。

関連記事

25
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
25
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?