为什么{} + {} NaN 只在客户端? 为什么不在 Node.js 中?

[] + []是一个空字符串,[] + {}"[object Object]"{} + []0。为什么是 {} + {} NaN?

> {} + {}
NaN

我的问题不是为什么 ({} + {}).toString()"[object Object][object Object]"NaN.toString()"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)
5033 次浏览

更新注释: 这个问题已经在 Chrome49中解决了

很有趣的问题,我们开始吧。

根本原因

不同之处在于 Node.js 对这些语句的评估方式和 Chrome 开发工具的评估方式。

Node.js 所做的

Js 使用 回复模块进行此操作。

来自 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 的行为不同

Js 的消息来源证明了这一点:

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

Node 并不总是这样做。这里是 改变它的实际提交。Ryan 对这个变化留下了如下的评论: “改进 REPL 命令的评估方式”,并举了一个差异的例子。


犀牛

Update-OP 感兴趣的是 犀牛的行为(以及为什么它的行为像 Chrome devtools 而不像 nodejs)。

Rhino 使用完全不同的 JS 引擎,不像 Chrome 开发工具和 Node.JS 的 REPL 都使用 V8。

下面是使用 Rhino shell 中的 Rhino 对 JavaScript 命令进行求值时发生的情况的基本管道。

基本上:

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

在这三种方法中,Rhino 的 shell 是最接近于实际 eval的方法,不需要任何包装。Rhino 的语句最接近于实际的 eval()语句,您可以期望它的行为与 eval完全一样。