Dlaczego {} + {} jest NaN tylko po stronie klienta? Dlaczego nie w Node.js?

136

While [] + []jest pustym łańcuchem, [] + {}jest "[object Object]"i {} + []jest 0. Dlaczego jest {} + {}NaN?

> {} + {}
  NaN

Moje pytanie jest dlaczego nie ({} + {}).toString()jest "[object Object][object Object]"natomiast NaN.toString()to "NaN", ta część ma odpowiedź już tutaj .

Moje pytanie brzmi: dlaczego dzieje się to tylko po stronie klienta? Po stronie serwera ( Node.js ) {} + {}jest "[object Object][object Object]".

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

Podsumowując :

Po stronie klienta:

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

W 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
źródło
4
Robi to tylko konsola przeglądarki. Spróbuj zalogować się do konsoli i jest to to samo, co w NodeJS. jsbin.com/oveyuj/1/edit
elclanrs
2
Nie jest to duplikat, proszę o odpowiedź NodeJS. Głosowanie za ponownym otwarciem ...
Ionică Bizău
4
Hmm ... przepraszam. Jednak stackoverflow.com/questions/9032856/… jest nadal aktualny i odpowiada pierwszej połowie
John Dvorak
3
Nie zapominaj, że w {}zależności od kontekstu można to zinterpretować jako wyrażenie lub jako obiekt pierwotny. Może kod jest taki sam na kliencie i na serwerze, ale interpretuje go {}inaczej ze względu na inny kontekst wprowadzania kodu.
Patashu
18
Otwórz ponownie, a następnie przestań ponownie zamykać to pytanie, ponieważ tak naprawdę nie jest ono duplikatem .
Alvin Wong

Odpowiedzi:

132

Zaktualizowana uwaga: zostało to naprawione w Chrome 49 .

Bardzo ciekawe pytanie! Zagłębmy się.

Podstawowa przyczyna

Źródłem różnicy jest sposób, w jaki Node.js ocenia te stwierdzenia, w porównaniu z narzędziami programistycznymi Chrome.

Co robi Node.js

Node.js używa do tego modułu repl .

Z kodu źródłowego REPL Node.js :

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

Działa to tak samo, jak działanie ({}+{})w narzędziach programistycznych Chrome, które również działają "[object Object][object Object]"zgodnie z oczekiwaniami.

Co robią narzędzia programistyczne Chrome

Z drugiej strony narzędzia Chrome dveloper wykonują następujące czynności :

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

Więc zasadniczo wykonuje a callna obiekcie z wyrażeniem. Wyrażenie to:

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

Zatem, jak widać, wyrażenie jest ewaluowane bezpośrednio, bez nawiasów zawijających.

Dlaczego Node.js działa inaczej

Źródło Node.js uzasadnia to:

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

Węzeł nie zawsze zachowywał się w ten sposób. Oto faktyczne zatwierdzenie, które go zmieniło . Ryan zostawił następujący komentarz dotyczący zmiany: „Popraw sposób oceny poleceń REPL” z przykładem różnicy.


Nosorożec

Aktualizacja - OP był zainteresowany tym, jak zachowuje się Rhino (i dlaczego zachowuje się jak Devtools Chrome iw przeciwieństwie do nodejs).

Rhino używa zupełnie innego silnika JS w przeciwieństwie do narzędzi programistycznych Chrome i REPL Node.js, które używają V8.

Oto podstawowa rura pokazująca, co się dzieje, gdy oceniasz polecenie JavaScript za pomocą Rhino w powłoce Rhino.

  • Pocisk działa org.mozilla.javascript.tools.shell.main.

  • Z kolei wywołuje to new IProxy(IProxy.EVAL_INLINE_SCRIPT); na przykład, jeśli kod został przekazany bezpośrednio za pomocą przełącznika wbudowanego -e.

  • To trafia w runmetodę IProxy .

  • Wywołuje evalInlineScript( src ). To po prostu kompiluje ciąg i ocenia go.

Gruntownie:

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

Spośród tych trzech, powłoka Rhino jest tą, która jest najbliższa rzeczywistej evalbez żadnego opakowania. Rhino's jest najbliżej rzeczywistego eval()stwierdzenia i możesz oczekiwać, że zachowa się dokładnie tak, jak evalby.

Benjamin Gruenbaum
źródło
1
(Nie jest to część odpowiedzi, ale warto wspomnieć, że nodejs domyślnie używa modułu vm do ewaluacji podczas korzystania z REPL, a nie tylko JavaScript eval)
Benjamin Gruenbaum
Czy możesz wyjaśnić, dlaczego na przykład nosorożec robi to samo w Terminalu (nie tylko w konsoli Chrome)?
Ionică Bizău
5
+10, gdyby to było możliwe! Wow stary, ... Naprawdę nie masz życia albo jesteś mądrzejszy ode mnie, żeby wiedzieć coś takiego. Proszę, powiedz mi, że szukałeś trochę, aby znaleźć tę odpowiedź :)
Samuel
7
@Samuel Wystarczyło przeczytać źródło - przysięgam! W przeglądarce Chrome, jeśli wpiszesz „debugger;” , otrzymasz całą potokę - przeniesie Cię bezpośrednio do „z” z tylko jedną funkcją powyżej do evaluateOn. W węźle wszystko jest bardzo dobrze udokumentowane - mają dedykowany moduł REPL z całą historią ładnie i przytulnie na git, po wcześniejszym używaniu REPL w moich własnych programach, wiedziałem, gdzie szukać :) Cieszę się, że Ci się podobało i znalazłem to pomocne, ale zawdzięczam to raczej znajomości tych podstaw kodu (dev-tools i nodejs) niż mojemu intelektowi. Często najłatwiej jest dotrzeć prosto do źródła.
Benjamin Gruenbaum
Aktualizacja - interfejs API konsoli w Chrome został nieco zaktualizowany, więc chociaż ogólny pomysł jest tutaj poprawny, opublikowany kod nie jest dokładny dla najnowszej wersji Chrome. Zobacz chromium.googlesource.com/chromium/blink.git/+/master/Source/…
Benjamin Gruenbaum