Perché il debugger di Chrome ritiene che la variabile locale chiusa non sia definita?


167

Con questo codice:

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

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

Ottengo questo risultato inaspettato:

inserisci qui la descrizione dell'immagine

Quando cambio il codice:

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

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

Ottengo il risultato atteso:

inserisci qui la descrizione dell'immagine

Inoltre, se c'è qualche chiamata evalall'interno della funzione interna, posso accedere alla mia variabile come voglio fare (non importa a cosa passo eval).

Nel frattempo, gli strumenti di sviluppo di Firefox forniscono il comportamento previsto in entrambe le circostanze.

Che succede con Chrome che il debugger si comporta in modo meno conveniente di Firefox? Ho osservato questo comportamento per qualche tempo, fino alla versione 41.0.2272.43 beta inclusa (64 bit).

Il motore javascript di Chrome "appiattisce" le funzioni quando può?

È interessante notare che se aggiungo una seconda variabile a cui fa riferimento la funzione interna, la xvariabile è ancora indefinita.

Capisco che ci sono spesso stranezze con ambito e definizione variabile quando si utilizza un debugger interattivo, ma mi sembra che in base alle specifiche del linguaggio ci dovrebbe essere una "migliore" soluzione a queste stranezze. Quindi sono molto curioso di sapere se ciò è dovuto all'ottimizzazione di Chrome oltre a Firefox. E anche se queste ottimizzazioni possono essere facilmente disabilitate durante lo sviluppo (forse dovrebbero essere disabilitate quando gli strumenti di sviluppo sono aperti?).

Inoltre, posso riprodurlo sia con punti di interruzione che con l' debuggeraffermazione.


2
forse ti sta
togliendo le

markle976 sembra dire che la debugger;linea non è effettivamente chiamata dall'interno bar. Quindi guarda la traccia dello stack quando si mette in pausa nel debugger: la barfunzione è menzionata nello stacktrace? Se ho ragione, allora lo stacktrace dovrebbe dire che è in pausa alla riga 5, alla riga 7, alla riga 9.
David Knipe,

Non penso che abbia nulla a che fare con le funzioni di appiattimento V8. Penso che sia solo una stranezza; Non so se lo definirei un bug. Penso che la risposta di David qui sotto abbia più senso.
markle976,


2
Ho lo stesso problema, lo odio. Ma quando ho bisogno di avere le voci di chiusura dell'accesso nella console, vado dove puoi vedere l'ambito, trovare la voce di chiusura e aprirla. Quindi fare clic con il tasto destro del mouse sull'elemento desiderato e fare clic su Store come variabile globale . Una nuova variabile globale temp1è collegata alla console ed è possibile utilizzarla per accedere alla voce dell'ambito.
Pablo,

Risposte:


149

Ho trovato un rapporto sul problema v8 che riguarda proprio quello che stai chiedendo.

Ora, per riassumere ciò che viene detto in quel rapporto sul problema ... v8 può memorizzare le variabili locali in una funzione nello stack o in un oggetto "contesto" che vive nell'heap. Allocerà le variabili locali nello stack purché la funzione non contenga alcuna funzione interna che si riferisca ad esse. È un'ottimizzazione . Se una funzione interna fa riferimento a una variabile locale, questa variabile verrà inserita in un oggetto di contesto (ovvero nell'heap anziché nello stack). Il caso di evalè speciale: se viene chiamato affatto da una funzione interna, tutte le variabili locali vengono inserite nell'oggetto contesto.

Il motivo per l'oggetto di contesto è che in generale è possibile restituire una funzione interna da quella esterna e quindi lo stack esistente durante l'esecuzione della funzione esterna non sarà più disponibile. Quindi qualsiasi cosa acceda alla funzione interna deve sopravvivere alla funzione esterna e vivere nell'heap piuttosto che nello stack.

Il debugger non può ispezionare quelle variabili che si trovano nello stack. Per quanto riguarda il problema riscontrato nel debug, un membro del progetto afferma :

L'unica soluzione che mi viene in mente è che ogni volta che devtools è attivo, eliminiamo tutto il codice e ricompiliamo con l'allocazione del contesto forzata. Ciò regredirebbe drammaticamente delle prestazioni con devtools abilitato però.

Ecco un esempio di "se una funzione interna fa riferimento alla variabile, inseriscila in un oggetto di contesto". Se si esegue questo sarete in grado di accedere xalla debuggerdichiarazione, anche se xviene utilizzato solo in foofunzione di, che non è mai chiamato !

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

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

  function bar() {
    debugger;
  };

  bar();
}
baz();

13
Hai trovato un modo per elaborare il codice? Mi piace usare il debugger come REPL e codice lì quindi trasferire il codice nei miei file. Ma spesso non è possibile in quanto le variabili che dovrebbero esserci non sono accessibili. Un semplice eval non lo farà. Sento un infinito per il ciclo potrebbe.
Ray Foss

In realtà non ho riscontrato questo problema durante il debug, quindi non ho cercato modi per estrarre il codice.
Louis

6
L'ultimo commento del problema dice: Mettere V8 in una modalità in cui tutto è forzatamente allocato nel contesto è possibile, ma non sono sicuro di come / quando attivarlo tramite l'interfaccia utente di Devtools Per motivi di debug, a volte vorrei farlo . Come posso forzare tale modalità?
Suma,

2
@ user208769 Quando si chiude come duplicato, si preferisce la domanda più utile per i futuri lettori. Esistono diversi fattori che aiutano a determinare quale domanda è più utile: la tua domanda ha ottenuto esattamente 0 risposte mentre questa ha ottenuto più risposte votate. Quindi questa domanda è la più utile delle due. Le date diventano un fattore determinante solo se l'utilità è per lo più uguale.
Louis,

1
Questa risposta risponde alla domanda effettiva (perché?), Ma alla domanda implicita: come posso accedere alle variabili di contesto non utilizzate per il debug senza aggiungere riferimenti aggiuntivi a esse nel mio codice? - è meglio rispondere da @OwnageIsMagic di seguito.
Sigfried,

30

Come ha detto @Louis, causato dalle ottimizzazioni di v8. Puoi attraversare lo stack di chiamate per inquadrare dove è visibile questa variabile:

Chiamata1 call2

O sostituire debuggercon

eval('debugger');

eval eliminerà il pezzo corrente


1
Quasi fantastico! Si interrompe in un modulo VM (giallo) con contenuti debuggere il contesto è effettivamente disponibile. Se aumenti lo stack di un livello al codice che stai effettivamente tentando di eseguire il debug, ritorni a non avere accesso al contesto. Quindi è solo leggermente goffo, non potendo guardare il codice di cui stai eseguendo il debug mentre accedi alle variabili di chiusura nascoste. Voterò, però, dal momento che mi evita di dover aggiungere codice che non è ovviamente per il debug e mi dà accesso a tutto il contesto senza deoptimizzare l'intera app.
Sigfried,

Oh ... è ancora più complicato che dover utilizzare la evalfinestra di origine gialla ed per accedere al contesto: non è possibile scorrere il codice (a meno che non si inserisca eval('debugger')tra tutte le righe che si desidera passare).
Sigfried

Sembra che ci siano situazioni in cui alcune variabili sono invisibili anche dopo aver attraversato il frame stack appropriato; Ho qualcosa di simile controllers.forEach(c => c.update())e un punto di interruzione da qualche parte nel profondo c.update(). Se poi seleziono il frame in cui controllers.forEach()viene chiamato, controllersnon è definito (ma tutto il resto in quel frame è visibile). Non sono riuscito a riprodurre con una versione minimale, suppongo che potrebbe esserci qualche soglia di complessità che deve essere superata o qualcosa del genere.
PeterT

@PeterT se è <undefined> sei nel posto sbagliato O il somewhere deep inside c.update()tuo codice diventa asincrono e vedi un frame di stack asincrono
OwnageIsMagic

6

L'ho notato anche in nodejs. Credo (e ammetto che questa è solo una supposizione) che quando il codice viene compilato, se xnon appare all'interno bar, non viene reso xdisponibile nell'ambito di bar. Questo probabilmente lo rende leggermente più efficiente; il problema è che qualcuno ha dimenticato (o non importava) che, anche se non c'è xin bar, si potrebbe decidere di eseguire il debugger e quindi ancora bisogno di accesso xdall'interno bar.


2
Grazie. Fondamentalmente voglio essere in grado di spiegare questo ai principianti di JavaScript meglio di "Il debugger mente".
Gabe Kopley,

@GabeKopley: tecnicamente il debugger non sta mentendo. Se una variabile non è referenziata, non è tecnicamente chiusa. Pertanto, non è necessario che l'interprete crei la chiusura.
slebetman,

7
Non è questo il punto. Quando si utilizza il debugger, mi sono spesso trovato in una situazione in cui volevo conoscere il valore di una variabile in un ambito esterno, ma a causa di ciò. E su una nota più filosofica, direi che il debugger sta mentendo. Se la variabile esiste nell'ambito interno non dovrebbe dipendere dal fatto che sia effettivamente utilizzata o se esiste un evalcomando non correlato . Se la variabile viene dichiarata, dovrebbe essere accessibile.
David Knipe,

2

Caspita, davvero interessante!

Come altri hanno già detto, questo sembra essere correlato scope, ma più specificamente, correlato debugger scope. Quando lo script iniettato viene valutato negli strumenti di sviluppo, sembra determinare a ScopeChain, il che si traduce in una certa stranezza (poiché è legata all'ambito dell'ispettore / debugger). Una variante di ciò che hai pubblicato è questa:

(EDIT - in realtà, lo dici nella tua domanda originale, yikes, my bad! )

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

Per l'ambizioso e / o curioso, cerca (eh) la fonte per vedere cosa sta succedendo:

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


0

Sospetto che ciò abbia a che fare con il sollevamento di variabili e funzioni. JavaScript porta tutte le dichiarazioni di variabili e funzioni in cima alla funzione in cui sono definite. Maggiori informazioni qui: http://jamesallardice.com/explaining-function-and-variable-hoisting-in-javascript/

Scommetto che Chrome sta chiamando il punto di interruzione con la variabile non disponibile nell'ambito perché non c'è nient'altro nella funzione. Questo sembra funzionare:

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

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

Come fa questo:

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

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

Spero che questo e / o il link sopra siano di aiuto. Queste sono le mie domande preferite su SO, BTW :)


Grazie! :) Mi chiedo cosa FF faccia diversamente. Dal mio punto di vista come sviluppatore, l'esperienza FF è oggettivamente migliore ...
Gabe Kopley,

2
"chiamando il punto di rottura al tempo lex" ne dubito. Non è a questo che servono i breakpoint. E non vedo perché l'assenza di altre cose nella funzione dovrebbe importare. Detto questo, se si tratta di qualcosa di simile a nodejs, i punti di interruzione potrebbero essere molto corretti.
David Knipe,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.