Почему {} + {} является NaN только на стороне клиента? Почему не в Node.js?

Пока [] + [] - пустая строка, [] + {} - "[object Object]", а {} + [] - 0. Почему {} + {} NaN?

> {} + {}
  NaN

Мой вопрос не в том, почему ({} + {}).toString() есть "[object Object][object Object]", в то время как NaN.toString() is "NaN", эта часть уже имеет ответ.

Мой вопрос: почему это происходит только на стороне клиента? На стороне сервера (Node.js) {} + {} - "[object Object][object Object]".

> {} + {}
'[object Object][object Object]'

Подведение итогов

На стороне клиента:

 [] + []              // Returns ""
 [] + {}              // Returns "[object Object]"
 {} + []              // Returns 0
 {} + {}              // Returns NaN

 NaN.toString()       // Returns "NaN"
 ({} + {}).toString() // Returns "[object Object][object Object]"
 var a = {} + {};     // 'a' will be "[object Object][object Object]"

В Node.js:

 [] + []   // Returns "" (like on the client)
 [] + {}   // Returns "[object Object]" (like on the client)
 {} + []   // Returns "[object Object]" (not like on the client)
 {} + {}   // Returns "[object Object][object Object]" (not like on the client)

Ответы

Ответ 1

Обновлено примечание: это было исправлено в Chrome 49.

Очень интересный вопрос! Позвольте вставить.

Основная причина

Корень различия заключается в том, как Node.js оценивает эти операторы, как это делают инструменты разработки Chrome.

Что Node.js делает

Node.js использует repl для этого.

Из Node.js Исходный код REPL:

self.eval(
    '(' + evalCmd + ')',
    self.context,
    'repl',
    function (e, ret) {
        if (e && !isSyntaxError(e))
            return finish(e);
        if (typeof ret === 'function' && /^[\r\n\s]*function/.test(evalCmd) || e) {
            // Now as statement without parens.
            self.eval(evalCmd, self.context, 'repl', finish);
        }
        else {
            finish(null, ret);
        }
    }
);

Это действует так же, как запуск ({}+{}) в инструментах разработчика Chrome, который также создает "[object Object][object Object]", как и следовало ожидать.

Что делают инструменты для создания хрома

С другой стороны Инструменты разработчика Chrome делают следующее:

try {
    if (injectCommandLineAPI && inspectedWindow.console) {
        inspectedWindow.console._commandLineAPI = new CommandLineAPI(this._commandLineAPIImpl, isEvalOnCallFrame ? object : null);
        expression = "with ((window && window.console && window.console._commandLineAPI) || {}) {\n" + expression + "\n}";
    }
    var result = evalFunction.call(object, expression);
    if (objectGroup === "console")
        this._lastResult = result;
    return result;
}
finally {
    if (injectCommandLineAPI && inspectedWindow.console)
        delete inspectedWindow.console._commandLineAPI;
}

В основном, он выполняет call на объекте с выражением. Выражение:

with ((window && window.console && window.console._commandLineAPI) || {}) {
    {}+{};// <-- This is your code
}

Итак, как вы можете видеть, выражение вычисляется напрямую, без скобок.

Почему Node.js действует по-разному

Node.js источник оправдывает это:

// This catches '{a : 1}' properly.

Node не всегда так поступал. Вот фактическая фиксация, которая изменила ее. Райан оставил следующий комментарий об изменении: "Улучшите, как команды REPL обнуляются", с примером разницы.


Rhino

Update - OP интересовался тем, как ведет себя Rhino (и почему он ведет себя как Chrome devtools и в отличие от nodejs).

Rhino использует совершенно другой JS-движок в отличие от инструментов разработчика Chrome и Node.js REPL, которые используют V8.

Вот базовая строка, что происходит, когда вы вычисляете команду JavaScript с Rhino в оболочке Rhino.

  • Оболочка запускает org.mozilla.javascript.tools.shell.main.

  • В свою очередь, он вызывает this new IProxy(IProxy.EVAL_INLINE_SCRIPT);, например, если код передавался непосредственно с встроенным коммутатором -e.

  • Это вызывает метод IProxy run.

  • Он вызывает evalInlineScript (src). Это просто компилирует строку и оценивает ее.

В принципе:

Script script = cx.compileString(scriptText, "<command>", 1, null);
if (script != null) {
    script.exec(cx, getShellScope()); // <- just an eval
}

Из трех оболочек Rhino - это тот, который делает самое близкое к фактическому eval без какой-либо обертки. Rhino ближе всего к фактическому оператору eval(), и вы можете ожидать, что он будет вести себя точно так же, как eval.