Mengapa {} + {} NaN hanya di sisi klien? Mengapa tidak di Node.js?

136

While [] + []adalah string kosong, [] + {}is "[object Object]", dan {} + []is 0. Mengapa {} + {}NaN?

> {} + {}
  NaN

Pertanyaan saya adalah tidak mengapa ({} + {}).toString()adalah "[object Object][object Object]"sementara NaN.toString()adalah "NaN", bagian ini memiliki jawaban di sini sudah .

Pertanyaan saya adalah mengapa ini hanya terjadi di sisi klien? Di sisi server ( Node.js ) {} + {}adalah "[object Object][object Object]".

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

Meringkas :

Di sisi klien:

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

Di 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
sumber
4
Hanya konsol browser yang melakukan itu. Coba masuk ke konsol dan itu sama seperti di NodeJS. jsbin.com/oveyuj/1/edit
elclanrs
2
Bukan duplikat, saya meminta jawaban NodeJS. Voting untuk dibuka kembali ...
Ionică Bizău
4
Hmm ... maaf. Namun, stackoverflow.com/questions/9032856/… masih relevan dan menjawab paruh pertama
John Dvorak
3
Jangan lupa itu {}bisa diartikan baik sebagai ekspresi atau sebagai objek primitif tergantung pada konteksnya. Mungkin kodenya sama di klien dan di server tetapi menafsirkannya {}berbeda karena konteks yang berbeda dalam memasukkan kode.
Patashu
18
Harap buka kembali lalu berhenti menutup pertanyaan ini lagi dan lagi karena pertanyaan ini sebenarnya bukan duplikat .
Alvin Wong

Jawaban:

132

Catatan yang diperbarui: ini telah diperbaiki di Chrome 49 .

Pertanyaan yang sangat menarik! Mari kita gali.

Akar penyebabnya

Akar perbedaannya terletak pada cara Node.js mengevaluasi pernyataan ini vs. cara alat pengembangan Chrome melakukannya.

Apa yang dilakukan Node.js

Node.js menggunakan modul repl untuk ini.

Dari kode sumber 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);
        }
    }
);

Ini berfungsi seperti berjalan ({}+{})di alat pengembang Chrome, yang juga menghasilkan "[object Object][object Object]"seperti yang Anda harapkan.

Apa yang dilakukan alat pengembang chrome

Di sisi lain, alat pengembang Chrome melakukan hal berikut :

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

Jadi pada dasarnya, ia melakukan a callpada objek dengan ekspresi. Ekspresinya adalah:

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

Jadi, seperti yang Anda lihat, ekspresi sedang dievaluasi secara langsung, tanpa tanda kurung pembungkus.

Mengapa Node.js bertindak berbeda

Sumber Node.js membenarkan ini:

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

Node tidak selalu bertindak seperti ini. Berikut adalah komit sebenarnya yang mengubahnya . Ryan meninggalkan komentar berikut tentang perubahan tersebut: "Perbaiki bagaimana perintah REPL dieval" dengan contoh perbedaannya.


Badak

Pembaruan - OP tertarik pada bagaimana Rhino berperilaku (dan mengapa ia berperilaku seperti devtools Chrome dan tidak seperti nodejs).

Rhino menggunakan mesin JS yang sama sekali berbeda tidak seperti alat pengembang Chrome dan REPL Node.js yang keduanya menggunakan V8.

Berikut adalah pipa saluran dasar tentang apa yang terjadi ketika Anda mengevaluasi perintah JavaScript dengan Rhino di cangkang Rhino.

Pada dasarnya:

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

Dari ketiganya, cangkang Rhino adalah yang paling mendekati kenyataan evaltanpa pembungkus. Rhino adalah yang paling dekat dengan eval()pernyataan aktual dan Anda dapat mengharapkannya berperilaku persis seperti yang diharapkan eval.

Benjamin Gruenbaum
sumber
1
(Tidak benar-benar merupakan bagian dari jawaban, tetapi perlu disebutkan nodejs menggunakan modul vm untuk evaling secara default saat menggunakan REPL, dan bukan hanya JavaScript eval)
Benjamin Gruenbaum
Bisakah Anda menjelaskan mengapa badak , misalnya, melakukan hal yang sama di Terminal (tidak hanya Konsol Chrome)?
Ionică Bizău
5
+10 jika memungkinkan! Wow man, ... Anda benar-benar tidak memiliki kehidupan atau Anda benar-benar lebih pintar dari saya untuk mengetahui sesuatu seperti itu. Tolong beritahu saya bahwa Anda mencari sedikit untuk menemukan jawaban ini :)
Samuel
7
@Samuel Yang dibutuhkan hanyalah membaca sumbernya - Aku bersumpah! Di Chrome, jika Anda memasukkan 'debugger;' , Anda mendapatkan seluruh pipa - ini akan mengarahkan Anda langsung ke 'dengan' hanya dengan satu fungsi di atas evaluateOn. Di node, semuanya didokumentasikan dengan sangat baik - mereka memiliki modul REPL khusus dengan semua sejarah yang bagus dan nyaman di git, setelah menggunakan REPL sebelumnya pada program saya sendiri, saya tahu di mana mencarinya :) Saya senang Anda menyukainya dan menemukannya itu membantu, tetapi saya berhutang pada pengetahuan saya dengan basis kode ini (dev-tools dan nodejs) daripada kecerdasan saya. Langsung ke sumbernya seringkali selalu menjadi yang termudah.
Benjamin Gruenbaum
Perbarui - API konsol di Chrome telah diperbarui sedikit, jadi meskipun gagasan umum di sini benar, kode yang diposting tidak akurat untuk versi terbaru Chrome. Lihat chromium.googlesource.com/chromium/blink.git/+/master/Source/…
Benjamin Gruenbaum