Mengapa debugger Chrome menganggap variabel lokal tertutup tidak terdefinisi?

167

Dengan kode ini:

function baz() {
  var x = "foo";

  function bar() {
    debugger;
  };
  bar();
}
baz();

Saya mendapatkan hasil yang tidak terduga ini:

masukkan deskripsi gambar di sini

Ketika saya mengubah kode:

function baz() {
  var x = "foo";

  function bar() {
    x;
    debugger;
  };
  bar();
}

Saya mendapatkan hasil yang diharapkan:

masukkan deskripsi gambar di sini

Juga, jika ada panggilan ke evaldalam fungsi dalam, saya dapat mengakses variabel saya seperti yang saya ingin lakukan (tidak peduli apa yang saya berikan eval).

Sementara itu, alat pengembang Firefox memberikan perilaku yang diharapkan dalam kedua keadaan.

Ada apa dengan Chrome yang berperilaku debugger kurang nyaman daripada Firefox? Saya telah mengamati perilaku ini selama beberapa waktu, hingga dan termasuk Versi 41.0.2272.43 beta (64-bit).

Apakah itu mesin javascript Chrome "meratakan" fungsi ketika itu bisa?

Menariknya jika saya menambahkan variabel kedua yang adalah direferensikan dalam fungsi batin, xvariabel masih terdefinisi.

Saya mengerti bahwa sering ada quirks dengan cakupan dan definisi variabel ketika menggunakan debugger interaktif, tetapi menurut saya berdasarkan spesifikasi bahasa seharusnya ada solusi "terbaik" untuk quirks ini. Jadi saya sangat ingin tahu apakah ini karena Chrome lebih mengoptimalkan daripada Firefox. Dan juga apakah optimasi ini dapat dengan mudah dinonaktifkan selama pengembangan (mungkin mereka harus dinonaktifkan ketika alat dev terbuka?).

Juga, saya dapat mereproduksi ini dengan breakpoints serta debuggerpernyataannya.

Gabe Kopley
sumber
2
mungkin itu membuat variabel yang tidak digunakan keluar dari jalan untuk Anda ...
dandavis
markle976 tampaknya mengatakan debugger;garis itu sebenarnya tidak dipanggil dari dalam bar. Jadi lihat jejak stack ketika ia berhenti di debugger: Apakah barfungsi disebutkan dalam stacktrace? Jika saya benar, maka stacktrace seharusnya mengatakan itu dijeda pada baris 5, pada baris 7, pada baris 9.
David Knipe
Saya tidak berpikir itu ada hubungannya dengan fungsi perataan V8. Saya pikir ini hanya kekhasan; Saya tidak tahu apakah saya akan menyebutnya bug. Saya pikir jawaban David di bawah ini paling masuk akal.
markle976
2
Saya memiliki masalah yang sama, saya benci itu. Tetapi ketika saya perlu memiliki entri penutupan akses di konsol, saya pergi ke tempat Anda dapat melihat ruang lingkup, menemukan entri Penutupan dan membukanya. Kemudian klik kanan pada elemen yang Anda butuhkan dan klik Store sebagai Global Variable . Variabel global baru temp1dilampirkan ke konsol dan Anda dapat menggunakannya untuk mengakses entri lingkup.
Pablo

Jawaban:

149

Saya telah menemukan laporan masalah v8 yang tepatnya tentang apa yang Anda minta.

Sekarang, Untuk meringkas apa yang dikatakan dalam laporan masalah itu ... v8 dapat menyimpan variabel yang lokal ke fungsi di stack atau dalam objek "konteks" yang hidup di heap. Ini akan mengalokasikan variabel lokal pada stack selama fungsi tersebut tidak mengandung fungsi bagian dalam yang merujuk padanya. Ini adalah optimasi . Jika ada fungsi dalam yang merujuk ke variabel lokal, variabel ini akan diletakkan di objek konteks (yaitu di heap, bukan di stack). Kasus evalkhusus: jika dipanggil sama sekali oleh fungsi dalam, semua variabel lokal dimasukkan ke dalam objek konteks.

Alasan untuk objek konteks adalah bahwa secara umum Anda bisa mengembalikan fungsi dalam dari yang luar dan kemudian tumpukan yang ada sementara fungsi luar berlari tidak akan tersedia lagi. Jadi apapun yang diakses fungsi bagian dalam harus selamat dari fungsi luar dan hidup di heap daripada di stack.

Debugger tidak dapat memeriksa variabel-variabel yang ada di tumpukan. Mengenai masalah yang ditemukan dalam debugging, salah satu Anggota Proyek mengatakan :

Satu-satunya solusi yang dapat saya pikirkan adalah bahwa setiap kali devtools aktif, kami akan membatalkan semua kode dan mengkompilasi ulang dengan alokasi konteks yang dipaksakan. Itu akan secara dramatis menurunkan kinerja dengan devtools yang diaktifkan.

Berikut adalah contoh dari "jika ada fungsi dalam merujuk pada variabel, letakkan di objek konteks". Jika Anda menjalankan ini, Anda akan dapat mengakses xpada debuggerpernyataan meskipun xhanya digunakan dalam foofungsi, yang tidak pernah dipanggil !

function baz() {
  var x = "x value";
  var z = "z value";

  function foo () {
    console.log(x);
  }

  function bar() {
    debugger;
  };

  bar();
}
baz();
Louis
sumber
13
Sudahkah Anda menemukan cara untuk membatalkan kode? Saya suka menggunakan debugger sebagai REPL dan kode di sana kemudian mentransfer kode ke file saya sendiri. Tetapi seringkali tidak layak karena variabel yang seharusnya ada tidak dapat diakses. Eval sederhana tidak akan melakukannya. Saya mendengar infinite untuk loop mungkin.
Ray Foss
Saya sebenarnya tidak mengalami masalah ini saat debugging jadi saya belum mencari cara untuk membatalkan kode.
Louis
6
Komentar terakhir dari masalah ini mengatakan: Menempatkan V8 ke mode di mana segala sesuatu dialokasikan secara konteks adalah mungkin, tetapi saya tidak yakin bagaimana / kapan harus memicu itu melalui Devtools UI Demi debugging, kadang-kadang saya ingin melakukannya . Bagaimana saya bisa memaksakan mode seperti itu?
Suma
2
@ user208769 Saat ditutup sebagai duplikat, kami menyukai pertanyaan yang paling berguna bagi pembaca di masa mendatang. Ada beberapa faktor yang membantu menentukan pertanyaan mana yang paling berguna: pertanyaan Anda mendapat 0 jawaban tepat sementara yang satu ini mendapat beberapa jawaban ter-upgrade. Jadi pertanyaan ini adalah yang paling berguna dari keduanya. Tanggal menjadi faktor penentu hanya jika kegunaannya sebagian besar sama.
Louis
1
Jawaban ini menjawab pertanyaan aktual (Mengapa?), Tetapi pertanyaan yang tersirat - Bagaimana saya mendapatkan akses ke variabel konteks yang tidak digunakan untuk debugging tanpa menambahkan referensi tambahan kepada mereka dalam kode saya? - lebih baik dijawab oleh @OwnageIsMagic di bawah ini.
Sigfried
30

Seperti @Louis mengatakan itu disebabkan oleh optimasi v8. Anda dapat melintasi tumpukan panggilan ke bingkai tempat variabel ini terlihat:

call1 call2

Atau ganti debuggerdengan

eval('debugger');

eval akan membatalkan potongan saat ini

OwnageIsMagic
sumber
1
Hampir hebat! Itu berhenti di modul VM (kuning) dengan konten debugger, dan konteks memang tersedia. Jika Anda meningkatkan tumpukan satu tingkat ke kode yang sebenarnya Anda coba debug, Anda kembali ke tidak memiliki akses ke konteks. Jadi itu hanya sedikit kikuk, tidak bisa melihat kode yang Anda debug saat mengakses variabel penutupan tersembunyi. Namun, saya akan lebih memilih, karena ini menyelamatkan saya dari keharusan menambahkan kode yang tidak jelas untuk debugging, dan itu memberi saya akses ke seluruh konteks tanpa menonaktifkan seluruh aplikasi.
Sigfried
Oh ... ini bahkan lebih clunkier daripada harus menggunakan evaljendela sumber ed kuning untuk mendapatkan akses ke konteks: Anda tidak dapat melangkah melalui kode (kecuali jika Anda meletakkan di eval('debugger')antara semua baris yang ingin Anda lewati.)
Sigfried
Tampaknya ada situasi di mana variabel tertentu tidak terlihat bahkan setelah melintasi ke bingkai tumpukan yang sesuai; Saya punya sesuatu seperticontrollers.forEach(c => c.update()) dan mencapai breakpoint di suatu tempat jauh di dalam c.update(). Jika saya kemudian memilih bingkai di mana controllers.forEach()dipanggil, controllerstidak terdefinisi (tetapi semua yang lain dalam bingkai itu terlihat). Saya tidak dapat mereproduksi dengan versi minimal, saya kira mungkin ada ambang batas kompleksitas yang perlu dilewati atau sesuatu.
PeterT
@PeterT jika <undefined> Anda berada di tempat yang salah Atau somewhere deep inside c.update()kode Anda menjadi async dan Anda melihat bingkai tumpukan async
OwnageIsMagic
6

Saya juga memperhatikan ini di nodejs. Saya percaya (dan saya akui ini hanya tebakan) bahwa ketika kode dikompilasi, jika xtidak muncul di dalam bar, itu tidak xtersedia di dalam lingkup bar. Ini mungkin membuatnya sedikit lebih efisien; masalahnya adalah seseorang lupa (atau tidak peduli) bahwa bahkan jika tidak ada xdi bar, Anda mungkin memutuskan untuk menjalankan debugger dan karenanya masih perlu akses xdari dalam bar.

David Knipe
sumber
2
Terima kasih. Pada dasarnya saya ingin dapat menjelaskan hal ini kepada pemula javascript lebih baik daripada "The debugger terletak."
Gabe Kopley
@ GabeKopley: Secara teknis debugger tidak berbohong. Jika variabel tidak direferensikan maka itu tidak tertutup secara teknis. Jadi tidak perlu bagi juru bahasa untuk membuat penutupan.
slebetman
7
Itu bukan intinya. Saat menggunakan debugger, saya sering berada dalam situasi di mana saya ingin mengetahui nilai variabel dalam lingkup luar, tetapi tidak bisa karena hal ini. Dan pada catatan yang lebih filosofis, saya akan mengatakan debugger itu bohong. Apakah variabel ada di lingkup dalam seharusnya tidak bergantung pada apakah itu benar-benar digunakan atau apakah ada evalperintah yang tidak terkait . Jika variabel dideklarasikan, itu harus dapat diakses.
David Knipe
2

Wow, sangat menarik!

Seperti yang disebutkan orang lain, ini sepertinya berkaitan dengan scope , tetapi lebih khusus, berkaitan dengan debugger scope. Ketika skrip yang disuntikkan dievaluasi dalam alat pengembang, sepertinya akan menentukan a ScopeChain, yang menghasilkan quirkiness (karena terikat pada lingkup inspektur / debugger). Variasi dari apa yang Anda poskan adalah ini:

(EDIT - sebenarnya, Anda menyebutkan ini dalam pertanyaan awal Anda, ya, salahku! )

function foo() {
  var x = "bat";
  var y = "man";

  function bar() {
    console.log(x); // logs "bat"

    debugger; // Attempting to access "y" throws the following
              // Uncaught ReferenceError: y is not defined
              // However, x is available in the scopeChain. Weird!
  }
  bar();
}
foo();

Untuk yang ambisius dan / atau ingin tahu, lingkup (heh) keluar sumber untuk melihat apa yang terjadi:

https://github.com/WebKit/webkit/tree/master/Source/JavaScriptCore/inspector https://github.com/WebKit/webkit/tree/master/Source/JavaScriptCore/debugger

Mendongkrak
sumber
0

Saya menduga ini ada hubungannya dengan variabel dan fungsi mengangkat. JavaScript membawa semua deklarasi variabel dan fungsi ke bagian atas fungsi tempat mereka didefinisikan. Info lebih lanjut di sini: http://jamesallardice.com/explaining-function-and-variable-hoisting-in-javascript/

Saya bertaruh bahwa Chrome memanggil break point dengan variabel tidak tersedia untuk lingkup karena tidak ada yang lain dalam fungsi. Ini sepertinya berhasil:

function baz() {
  var x = "foo";

  function bar() {
    console.log(x); 
    debugger;
  };
  bar();
}

Seperti ini:

function baz() {
  var x = "foo";

  function bar() {
    debugger;
    console.log(x);     
  };
  bar();
}

Semoga ini, dan / atau tautan di atas membantu. Ini adalah jenis pertanyaan SO favorit saya, BTW :)

markle976
sumber
Terima kasih! :) Saya bertanya-tanya apa yang FF lakukan secara berbeda. Dari sudut pandang saya sebagai pengembang, pengalaman FF secara objektif lebih baik ...
Gabe Kopley
2
"Memanggil break point pada waktu lex" Aku ragu. Bukan itu tujuan breakpoints. Dan saya tidak melihat mengapa tidak adanya hal-hal lain dalam fungsi itu penting. Karena itu, jika itu seperti nodejs maka breakpoints mungkin sangat buggy.
David Knipe