Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
152 views
in Technique[技术] by (71.8m points)

javascript - Why {} + {} is NaN only on the client side? Why not in Node.js?

While [] + [] is an empty string, [] + {} is "[object Object]", and {} + [] is 0. Why is {} + {} NaN?

> {} + {}
  NaN

My question isn't why ({} + {}).toString() is "[object Object][object Object]" while NaN.toString() is "NaN", this part has an answer here already.

My question is why does this happen only on the client side? On the server side (Node.js) {} + {} is "[object Object][object Object]".

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

Summarizing:

On the client side:

 [] + []              // 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]"

In 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)
Question&Answers:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

Updated note: this has been fixed in Chrome 49.

Very interesting question! Let's dig in.

The root cause

The root of the difference is in how Node.js evaluates these statements vs. how the Chrome development tools do.

What Node.js does

Node.js uses the repl module for this.

From the Node.js REPL source code:

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

This acts just like running ({}+{}) in the Chrome developer tools, which also produces "[object Object][object Object]" as you'd expect.

What the chrome developer tools do

On the other hand Chrome dveloper tools does the following:

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

So basically, it performs a call on the object with the expression. The expression being:

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

So, as you can see, the expression is being evaluted directly, without the wrapping parenthesis.

Why Node.js acts differently

Node.js's source justifies this:

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

Node did not always act like this. Here is the actual commit that changed it. Ryan left the following comment on the change: "Improve how REPL commands are evaled" with an example of the difference.


Rhino

Update - OP was interested in how Rhino behaves (and why it behaves like the Chrome devtools and unlike nodejs).

Rhino uses a completely different JS engine unlike the Chrome developer tools and Node.js's REPL which both use V8.

Here is the basic pipe line of what happens when you eval a JavaScript command with Rhino in the Rhino shell.

Basically:

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

Out of the three, Rhino's shell is the one that does the closest thing to an actual eval without any wrapping. Rhino's is the closest to an actual eval() statement and you can expect it to behave exactly like eval would.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...