Por que {} + {} o NaN é apenas no lado do cliente? Por que não no Node.js?

136

Enquanto [] + []é uma string vazia, [] + {}é "[object Object]"e {} + []é 0. Por que {} + {}NaN?

> {} + {}
  NaN

Minha pergunta não é por que ({} + {}).toString()é "[object Object][object Object]"enquanto NaN.toString()é "NaN", esta parte já tem uma resposta aqui .

Minha pergunta é por que isso acontece apenas no lado do cliente? No lado do servidor ( Node.js ) {} + {}é "[object Object][object Object]".

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

Resumindo :

No lado do cliente:

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

No 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)
Ionică Bizău
fonte
4
É apenas o console do navegador que faz isso. Tente fazer login para o console e é o mesmo que em NodeJS. jsbin.com/oveyuj/1/edit
elclanrs
2
Não é realmente uma duplicata, estou pedindo a resposta do NodeJS. Votando para reabrir ...
Jonas Biz
4
Hmm ... desculpe. No entanto, stackoverflow.com/questions/9032856/... ainda é relevante e responde à primeira metade
John Dvorak
3
Não esqueça que isso {}pode ser interpretado como uma expressão ou como um objeto primitivo, dependendo do contexto. Talvez o código seja o mesmo no cliente e no servidor, mas esteja interpretando de maneira {}diferente devido ao contexto diferente da inserção do código.
Patashu
18
Reabra e, em seguida, pare de fechar esta pergunta novamente, pois essa pergunta não é realmente uma duplicata .
Alvin Wong

Respostas:

132

Nota atualizada: isso foi corrigido no Chrome 49 .

Pergunta muito interessante! Vamos cavar.

A causa raiz

A raiz da diferença está na maneira como o Node.js avalia essas instruções em comparação com o desempenho das ferramentas de desenvolvimento do Chrome.

O que o Node.js faz

O Node.js usa o módulo de substituição para isso.

Desde o Node.js código fonte 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);
        }
    }
);

Isso funciona como a execução ({}+{})nas ferramentas de desenvolvedor do Chrome, que também produzem "[object Object][object Object]"como você esperaria.

O que as ferramentas de desenvolvedor do Chrome fazem

Por outro lado, as ferramentas do Chrome Dveloper fazem o seguinte :

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;
}

Então, basicamente, ele executa um callno objeto com a expressão A expressão sendo:

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

Portanto, como você pode ver, a expressão está sendo avaliada diretamente, sem o parêntese de quebra automática.

Por que o Node.js age de maneira diferente

A fonte do Node.js justifica isso:

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

O nó nem sempre agia assim. Aqui está o commit real que o mudou . Ryan deixou o seguinte comentário sobre a alteração: "Melhore como os comandos REPL são avaliados" com um exemplo da diferença.


Rinoceronte

Atualização - O OP estava interessado em como o Rhino se comporta (e por que ele se comporta como os devtools do Chrome e diferente dos nodejs).

O Rhino usa um mecanismo JS completamente diferente das ferramentas de desenvolvedor do Chrome e do REPL do Node.js., que usam a V8.

Aqui está a linha de tubulação básica do que acontece quando você avalia um comando JavaScript com o Rhino no shell do Rhino.

Basicamente:

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

Dos três, a concha do Rhino é a que mais se aproxima de um real, evalsem qualquer acondicionamento. O Rhino's é o mais próximo de uma eval()afirmação real e você pode esperar que ela se comporte exatamente como evalfaria.

Benjamin Gruenbaum
fonte
1
(Não é realmente uma parte da resposta, mas vale a pena mencionar nodejs usa o módulo de vm para evaling por padrão quando usando o REPL, e não apenas um JavaScript eval)
Benjamin Gruenbaum
Você pode explicar por que o rhino , por exemplo, faz a mesma coisa no Terminal (não apenas no Chrome Console)?
Jonic Biză
5
+10 se fosse possível! Uau cara, ... Você realmente não tem vida ou é muito mais esperto que eu para saber algo assim. Por favor, me diga que você procurou um pouco para acharam esta resposta :)
Samuel
7
@ Samuel Tudo o que precisou é ler a fonte - eu juro! No Chrome, se você digitar 'depurador;' , você obtém o pipe inteiro - ele o levará diretamente ao 'with' com apenas uma função acima para evaluateOn. No nó, tudo está muito bem documentado - eles têm um módulo REPL dedicado com toda a história agradável e aconchegante no git, tendo usado REPLs antes em meus próprios programas, eu sabia onde procurar :) Estou feliz que você tenha gostado e encontrado útil, mas devo isso à minha familiaridade com essas bases de código (dev-tools e nodejs), e não ao meu intelecto. Ir direto à fonte geralmente é sempre o mais fácil.
Benjamin Gruenbaum
Atualização - a API do console no Chrome foi atualizada um pouco. Embora a idéia geral aqui esteja correta, o código postado não é preciso para a versão mais recente do Chrome. Veja chromium.googlesource.com/chromium/blink.git/+/master/Source/…
Benjamin Gruenbaum