¿Por qué {} + {} es NaN solo en el lado del cliente? ¿Por qué no en Node.js?

136

Mientras que [] + []es una cadena vacía, [] + {}es "[object Object]"y {} + []es 0. ¿Por qué es {} + {}NaN?

> {} + {}
  NaN

Mi pregunta no es por qué ({} + {}).toString()es "[object Object][object Object]"mientras que NaN.toString()es "NaN", esta parte tiene una respuesta aquí ya .

Mi pregunta es ¿por qué sucede esto solo en el lado del cliente? En el lado del servidor ( Node.js ) {} + {}está "[object Object][object Object]".

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

Resumiendo :

En el lado del 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]"

En 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
fuente
44
Es solo la consola del navegador que hace eso. Intente iniciar sesión en la consola y es lo mismo que en NodeJS. jsbin.com/oveyuj/1/edit
elclanrs
2
Realmente no es un duplicado, estoy pidiendo la respuesta de NodeJS. Votar por reabrir ...
Ionică Bizău
44
Hmm ... lo siento. Sin embargo, stackoverflow.com/questions/9032856/… sigue siendo relevante y responde a la primera mitad
John Dvorak
3
No olvide que {}puede interpretarse como una expresión o como un objeto primitivo según el contexto. Tal vez el código sea el mismo en el cliente y en el servidor, pero se interpreta de manera {}diferente debido al contexto diferente de ingresar el código.
Patashu
18
Vuelva a abrir y luego deje de cerrar esta pregunta una y otra vez, ya que esta pregunta realmente no es un duplicado .
Alvin Wong

Respuestas:

132

Nota actualizada: esto se ha solucionado en Chrome 49 .

Muy interesante pregunta! Vamos a profundizar en.

La causa principal

La raíz de la diferencia está en cómo Node.js evalúa estas declaraciones frente a cómo lo hacen las herramientas de desarrollo de Chrome.

Lo que hace Node.js

Node.js usa el módulo repl para esto.

Del código fuente 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);
        }
    }
);

Esto actúa como si se ejecutara ({}+{})en las herramientas de desarrollador de Chrome, que también produce "[object Object][object Object]"lo que cabría esperar.

Lo que hacen las herramientas para desarrolladores de Chrome

Por otro lado, las herramientas de Chrome Dveloper hacen lo siguiente :

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

Entonces, básicamente, realiza un callen el objeto con la expresión. La expresión es:

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

Entonces, como puede ver, la expresión se está evaluando directamente, sin el paréntesis de ajuste.

Por qué Node.js actúa de manera diferente

La fuente de Node.js justifica esto:

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

Nodo no siempre actuaba así. Aquí está la confirmación real que lo cambió . Ryan dejó el siguiente comentario sobre el cambio: "Mejore cómo se evaden los comandos REPL" con un ejemplo de la diferencia.


Rinoceronte

Actualización: OP estaba interesado en cómo se comporta Rhino (y por qué se comporta como las herramientas de Chrome y a diferencia de nodejs).

Rhino utiliza un motor JS completamente diferente a diferencia de las herramientas de desarrollador de Chrome y REPL de Node.js, que usan V8.

Aquí está la línea básica de lo que sucede cuando evalúa un comando de JavaScript con Rhino en el shell de Rhino.

Básicamente:

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

De los tres, el caparazón de Rhino es el que hace lo más parecido a un real evalsin ningún tipo de envoltura. Rhino's es el más cercano a una eval()declaración real y puede esperar que se comporte exactamente como lo evalharía.

Benjamin Gruenbaum
fuente
1
(No es realmente una parte de la respuesta, pero vale la pena mencionar que nodejs usa el módulo vm para evaluar de forma predeterminada cuando se usa REPL, y no solo un JavaScript eval)
Benjamin Gruenbaum
¿Puede explicar por qué Rhino , por ejemplo, hace lo mismo en la Terminal (no solo en la Consola de Chrome)?
Ionică Bizău
55
¡+10 si fuera posible! Wow hombre, ... Realmente no tienes vida o eres realmente más inteligente que yo para saber algo así. Por favor, dime que buscaste un poco para encontrar esta respuesta :)
Samuel
77
@Samuel Todo lo que tomó fue leer la fuente, ¡lo juro! En Chrome, si ingresas 'depurador'; , obtienes toda la tubería: te lanzará directamente al 'con' con solo una función de arriba a evaluateOn. En el nodo, todo está muy bien documentado: tienen un módulo REPL dedicado con toda la historia agradable y acogedor en git, habiendo usado REPL antes en mis propios programas, sabía dónde buscar :) Me alegra que te haya gustado y hayas encontrado es útil, pero se lo debo a mi familiaridad con estas bases de código (dev-tools y nodejs) en lugar de mi intelecto. Ir directamente a la fuente suele ser siempre lo más fácil.
Benjamin Gruenbaum
Actualización: la API de la consola en Chrome se ha actualizado un poco, por lo que aunque la idea general aquí es correcta, el código publicado no es exacto para la versión más reciente de Chrome. Ver chromium.googlesource.com/chromium/blink.git/+/master/Source/…
Benjamin Gruenbaum