¿Por qué el depurador de Chrome cree que la variable local cerrada no está definida?

167

Con este código:

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

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

Me sale este resultado inesperado:

ingrese la descripción de la imagen aquí

Cuando cambio el código:

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

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

Obtengo el resultado esperado:

ingrese la descripción de la imagen aquí

Además, si hay alguna llamada a evalla función interna, puedo acceder a mi variable como quiero hacerlo (no importa a lo que le pase eval).

Mientras tanto, las herramientas de desarrollo de Firefox ofrecen el comportamiento esperado en ambas circunstancias.

¿Qué pasa con Chrome que el depurador se comporta de manera menos conveniente que Firefox? He observado este comportamiento durante algún tiempo, hasta la versión 41.0.2272.43 beta incluida (64 bits).

¿Es que el motor de JavaScript de Chrome "aplana" las funciones cuando puede?

Curiosamente, si agrego una segunda variable a la que se hace referencia en la función interna, la xvariable aún no está definida.

Entiendo que a menudo hay peculiaridades con el alcance y la definición de variables cuando se usa un depurador interactivo, pero me parece que, según la especificación del lenguaje, debería haber una "mejor" solución para estas peculiaridades. Así que tengo mucha curiosidad si esto se debe a la optimización de Chrome más allá de Firefox. Y también si estas optimizaciones pueden deshabilitarse fácilmente durante el desarrollo (¿tal vez deberían deshabilitarse cuando las herramientas de desarrollo están abiertas?).

Además, puedo reproducir esto con puntos de interrupción, así como la debuggerdeclaración.

Gabe Kopley
fuente
2
tal vez está sacando de tu camino las variables no utilizadas ...
dandavis
markle976 parece estar diciendo que la debugger;línea en realidad no se llama desde adentro bar. Observe el seguimiento de la pila cuando se detiene en el depurador: ¿se barmenciona la función en el seguimiento de la pila? Si estoy en lo cierto, entonces el stacktrace debería decir que está en pausa en la línea 5, en la línea 7, en la línea 9.
David Knipe
No creo que tenga nada que ver con las funciones de aplanamiento V8. Creo que esto es solo una peculiaridad; No sé si incluso lo llamaría un error. Creo que la respuesta de David a continuación tiene más sentido.
markle976
2
Tengo el mismo problema, lo odio. Pero cuando necesito tener entradas de cierre de acceso en la consola, voy a donde puede ver el alcance, encontrar la entrada de Cierre y abrirla. Luego haga clic derecho en el elemento que necesita y haga clic en Almacenar como variable global . Se temp1adjunta una nueva variable global a la consola y puede usarla para acceder a la entrada del alcance.
Pablo

Respuestas:

149

He encontrado un informe de problema de v8 que trata precisamente de lo que estás preguntando.

Ahora, para resumir lo que se dice en ese informe de problema ... v8 puede almacenar las variables que son locales para una función en la pila o en un objeto de "contexto" que vive en el montón. Asignará variables locales en la pila siempre que la función no contenga ninguna función interna que se refiera a ellas. Es una optimización . Si alguna función interna se refiere a una variable local, esta variable se colocará en un objeto de contexto (es decir, en el montón en lugar de en la pila). El caso de evales especial: si es invocado por una función interna, todas las variables locales se colocan en el objeto de contexto.

La razón del objeto de contexto es que, en general, podría devolver una función interna de la externa y luego la pila que existía mientras se ejecutaba la función externa ya no estará disponible. Entonces, cualquier cosa a la que acceda la función interna debe sobrevivir a la función externa y vivir en el montón en lugar de en la pila.

El depurador no puede inspeccionar aquellas variables que están en la pila. Con respecto al problema encontrado en la depuración, un miembro del proyecto dice :

La única solución que se me ocurre es que cada vez que devtools esté activado, desoptaríamos todo el código y volveríamos a compilar con la asignación de contexto forzada. Sin embargo, eso reduciría drásticamente el rendimiento con devtools habilitado.

Aquí hay un ejemplo de "si alguna función interna se refiere a la variable, póngala en un objeto de contexto". Si ejecuta esto, podrá acceder xa la debuggerinstrucción aunque xsolo se use en la foofunción, que nunca se llama .

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

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

  function bar() {
    debugger;
  };

  bar();
}
baz();
Louis
fuente
13
¿Has descubierto una manera de desoptizar el código? Me gusta usar el depurador como REPL y codificar allí y luego transferir el código a mis propios archivos. Pero a menudo no es factible ya que las variables que deberían estar allí no son accesibles. Una simple evaluación no lo hará. Escucho un infinito for loop might.
Ray Foss
En realidad, no me he encontrado con este problema durante la depuración, por lo que no he buscado formas de deshabilitar el código.
Louis
66
El último comentario del problema dice: Poner V8 en un modo en el que todo se asigna por contexto a la fuerza es posible, pero no estoy seguro de cómo / cuándo activarlo a través de la interfaz de usuario de Devtools En aras de la depuración, a veces me gustaría hacerlo. . ¿Cómo puedo forzar tal modo?
Suma
2
@ user208769 Al cerrar como duplicado, favorecemos la pregunta que sea más útil para futuros lectores. Hay varios factores que ayudan a determinar qué pregunta es más útil: su pregunta obtuvo exactamente 0 respuestas, mientras que esta obtuvo múltiples respuestas con votos positivos. Entonces esta pregunta es la más útil de las dos. Las fechas se convierten en un factor determinante solo si la utilidad es mayormente igual.
Louis
1
Esta respuesta responde la pregunta real (¿Por qué?), Pero la pregunta implícita: ¿cómo obtengo acceso a las variables de contexto no utilizadas para la depuración sin agregar referencias adicionales a ellas en mi código? - Se responde mejor por @OwnageIsMagic a continuación.
Sigfried
30

Como @Louis dijo que fue causado por las optimizaciones v8. Puede recorrer la pila de llamadas para enmarcar donde esta variable es visible:

llamada1 llamada2

O reemplazar debuggercon

eval('debugger');

eval va a desarmar la porción actual

OwnageIsMagic
fuente
1
Casi genial! Hace una pausa en un módulo VM (amarillo) con contenido debugger, y el contexto está disponible. Si sube la pila un nivel al código que realmente está intentando depurar, volverá a no tener acceso al contexto. Por lo tanto, es un poco torpe, no poder ver el código que está depurando al acceder a las variables de cierre ocultas. Sin embargo, votaré, ya que me evita tener que agregar código que obviamente no es para depurar, y me da acceso a todo el contexto sin desoptimizar toda la aplicación.
Sigfried
Oh ... es aún más complicado que tener que usar la evalventana de origen ed amarilla para obtener acceso al contexto: no puedes pasar por el código (a menos que pongas eval('debugger')entre todas las líneas por las que quieres pasar).
Sigfried
Parece que hay situaciones en las que ciertas variables son invisibles incluso después de atravesar el marco de pila apropiado; Tengo algo así controllers.forEach(c => c.update())y llegué a un punto de interrupción en algún lugar muy profundo c.update(). Si luego selecciono el marco donde controllers.forEach()se llama, controllersno está definido (pero todo lo demás en ese marco es visible). No pude reproducir con una versión mínima, supongo que puede haber algún umbral de complejidad que deba pasarse o algo así.
PeterT
@PeterT si está <indefinido> está en el lugar equivocado O somewhere deep inside c.update()su código se vuelve asíncrono y ve un marco de pila asíncrono
OwnageIsMagic
6

También he notado esto en nodejs. Creo (y admito que esto es solo una suposición) que cuando se compila el código, si xno aparece dentro bar, no está xdisponible dentro del alcance de bar. Esto probablemente lo hace un poco más eficiente; el problema es que alguien se olvidó (o no le importaba) que incluso si no hay es xen bar, puede decidir ejecutar el depurador y, por tanto, aún es necesario el acceso xdesde el interior bar.

David Knipe
fuente
2
Gracias. Básicamente quiero poder explicar esto a los principiantes de JavaScript mejor que "El depurador miente".
Gabe Kopley
@ GabeKopley: Técnicamente el depurador no está mintiendo. Si una variable no está referenciada, entonces no está técnicamente encerrada. Por lo tanto, no es necesario que el intérprete cree el cierre.
slebetman
77
Ese no es el punto. Cuando uso el depurador, con frecuencia he estado en una situación en la que quería saber el valor de una variable en un ámbito externo, pero no pude debido a esto. Y en una nota más filosófica, diría que el depurador está mintiendo. Si la variable existe en el ámbito interno no debería depender de si se usa realmente o si hay un evalcomando no relacionado . Si se declara la variable, debería ser accesible.
David Knipe el
2

Wow, muy interesante!

Como otros han mencionado, esto parece estar relacionado con scope, pero más específicamente, relacionado con debugger scope. Cuando la secuencia de comandos inyectada se evalúa en las herramientas del desarrollador, parece determinar a ScopeChain, lo que resulta en una peculiaridad (ya que está vinculado al alcance del inspector / depurador). Una variación de lo que publicaste es esta:

(EDITAR: en realidad, mencionas esto en tu pregunta original, ¡ ay, qué mal! )

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

Para los ambiciosos y / o curiosos, busque (heh) la fuente para ver qué sucede:

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

Jack
fuente
0

Sospecho que esto tiene que ver con la elevación de variables y funciones. JavaScript lleva todas las declaraciones de variables y funciones a la parte superior de la función en la que se definen. Más información aquí: http://jamesallardice.com/explaining-function-and-variable-hoisting-in-javascript/

Apuesto a que Chrome está llamando al punto de ruptura con la variable no disponible para el alcance porque no hay nada más en la función. Esto parece funcionar:

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

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

Como hace esto:

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

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

Espero esto, y / o el enlace de arriba ayuda. Estas son mi tipo favorito de preguntas SO, por cierto :)

markle976
fuente
¡Gracias! :) Me pregunto qué hace FF de manera diferente. Desde mi perspectiva como desarrollador, la experiencia FF es objetivamente mejor ...
Gabe Kopley
2
"llamando al punto de quiebre a la hora de Lex" Lo dudo. Para eso no son los puntos de quiebre. Y no veo por qué debería importar la ausencia de otras cosas en la función. Dicho esto, si se trata de algo así como nodejs, los puntos de interrupción pueden ser muy defectuosos.
David Knipe