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

Con questo codice:

function baz() { var x = "foo"; function bar() { debugger; }; bar(); } baz(); 

Ottengo questo risultato inaspettato:

inserisci la descrizione dell'immagine qui

Quando cambio il codice:

 function baz() { var x = "foo"; function bar() { x; debugger; }; bar(); } 

Ottengo il risultato atteso:

inserisci la descrizione dell'immagine qui

Inoltre, se c’è una chiamata a eval all’interno della funzione interiore, posso accedere alla mia variabile come voglio fare (non importa ciò che passo per eval ).

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

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

È che il motore javascript di Chrome “appiattisce” le funzioni quando può?

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

Capisco che ci siano spesso stranezze con scope e definizione delle variabili quando si utilizza un debugger interattivo, ma mi sembra che sulla base delle specifiche del linguaggio ci dovrebbe essere una soluzione “migliore” a queste stranezze. Quindi sono molto curioso se questo è dovuto a Chrome che ottimizza 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 con i breakpoint e l’istruzione debugger .

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

Ora, per riassumere ciò che viene detto in quel rapporto di problema … v8 può memorizzare le variabili che sono locali in una funzione nello stack o in un object “contesto” che vive nell’heap. Alloca le variabili locali nello stack fintanto che la funzione non contiene alcuna funzione interna che si riferisce ad esse. È un’ottimizzazione . Se una qualsiasi funzione interiore si riferisce a una variabile locale, questa variabile sarà messa in un object di contesto (cioè sull’heap invece che sullo stack). Il caso di eval è speciale: se viene chiamato da una funzione interna, tutte le variabili locali vengono inserite nell’object di contesto.

Il motivo per l’object di contesto è che in generale è ansible restituire una funzione interna da quella esterna e quindi la pila esistente mentre la funzione esterna eseguita non sarà più disponibile. Quindi qualsiasi cosa acceda alla funzione interna deve sopravvivere alla funzione esterna e vivere sull’heap piuttosto che sullo stack.

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

L’unica soluzione che potrei pensare è che ogni volta che devtools è attivo, deottiamo tutto il codice e ricompiliamo con l’allocazione del contesto forzata. Ciò però regredirebbe drammaticamente le prestazioni con i devtools abilitati.

Ecco un esempio di “se una qualsiasi funzione interna si riferisce alla variabile, mettila in un object di contesto”. Se lo esegui, sarai in grado di accedere a x debugger anche se x è usato solo nella funzione foo , che non viene mai chiamata !

 function baz() { var x = "x value"; var z = "z value"; function foo () { console.log(x); } function bar() { debugger; }; bar(); } baz(); 

Come @Louis ha detto che è causato da ottimizzazioni v8. Puoi attraversare lo stack delle chiamate per inquadrare dove questa variabile è visibile:

Chiamata1 call2

Oppure sostituire il debugger con

 eval('debugger'); 

eval rimuoverà il pezzo corrente

Ho anche notato questo in nodejs. Credo (e ammetto che questa è solo un’ipotesi) che quando il codice è compilato, se x non appare all’interno della bar , non rende x disponibile all’interno dell’ambito della bar . Questo probabilmente lo rende leggermente più efficiente; il problema è che qualcuno ha dimenticato (o non gli importava) che anche se non ci fosse x nella bar , potreste decidere di eseguire il debugger e quindi avere ancora accesso a x dalla bar interna.

Wow, davvero interessante!

Come altri hanno menzionato, questo sembra essere correlato scope , ma più specificamente, relativo debugger scope . Quando lo script iniettato viene valutato negli strumenti di sviluppo, sembra determinare uno ScopeChain , il che si traduce in qualche stranezza (poiché è legata all’ambito ispettore / debugger). Una variante di ciò che hai postato è questa:

(MODIFICA – in realtà, lo menzioni nella tua domanda iniziale, ehi, mio ​​male! )

 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 gli ambiziosi e / o curiosi, spetta (heh) 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

Sospetto che questo abbia a che fare con il sollevamento di variabili e funzioni. JavaScript porta tutte le dichiarazioni di variabili e funzioni all’inizio della funzione in cui sono definite. Ulteriori 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 per l’ambito perché non c’è nient’altro nella funzione. Questo sembra funzionare:

 function baz() { var x = "foo"; function bar() { console.log(x); debugger; }; bar(); }