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

[] + []是空string时, [] + {}"[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) 

更新说明: 这已在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开发者工具的function

另一方面, 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; } 

所以基本上,它使用expression式对对象执行call 。 expression是:

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

所以,正如你所看到的,expression式正在被直接评估,没有包装的括号。

为什么Node.js的行为不同

Node.js的来源certificate了这一点:

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

节点并不总是这样。 这是改变它的实际提交 。 Ryan对这个改变留下了下面的评论:“改进REPL命令如何被撤销”,举个例子。


犀牛

更新 – OP对Rhino的行为感兴趣(为什么它的行为像Chrome devtools,而不像nodejs)。

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

以下是在Rhino shell中使用Rhino评估JavaScript命令时发生的基本pipe线。

  • 该shell运行org.mozilla.javascript.tools.shell.main

  • 反过来,它会调用这个 new IProxy(IProxy.EVAL_INLINE_SCRIPT); 例如,如果代码直接通过内联开关-e传递。

  • 这点击IProxy的run方法。

  • 它调用evalInlineScript ( src )。 这只是编译string和eval它。

基本上:

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

犀牛的shell是三个最接近实际eval的shell,没有任何包装。 犀牛是最接近实际的eval()声明,你可以期望它的行为完全一样eval会。