Obteniendo todas las variables en alcance

194

¿Hay alguna manera de obtener todas las variables que están actualmente en alcance en JavaScript?

Ian
fuente
Responda a Camsoft: esa es una pregunta totalmente diferente; He actualizado mi respuesta para abordarlo.
TJ Crowder
3
Es solo una pregunta general, ser más específico no ayudará mucho ya que estoy trabajando con una API oscura con poca documentación.
Ian
¡Te refieres a variables globales! Puede mostrar las variables globales enumerables usando for (v in this) alert(v);. Sin embargo, no todos los globales son enumerables, y no conozco ninguna forma estándar de obtener una lista de los no enumerables.
Jason Orendorff
2
@ Jason - No, la pregunta es clara. Dentro de una función de las variables en el alcance incluirán variables globales, this, arguments, parámetros y todas las variables definidas en ámbitos que encierra.
Tim Down
2
Es por eso que extraño las tablas de símbolos de Perl. ¿Algún plan para agregar esto a una versión futura de Javascript?
Dexygen

Respuestas:

84

No. Las variables "en alcance" están determinadas por la "cadena de alcance", que no es accesible mediante programación.

Para obtener detalles (bastante), consulte la especificación ECMAScript (JavaScript). Aquí hay un enlace a la página oficial donde puede descargar la especificación canónica (un PDF), y aquí está uno a la versión HTML oficial y enlazable.

Actualización basada en tu comentario a Camsoft

Las variables en el alcance de su función de evento están determinadas por dónde define su función de evento, no cómo lo llaman. Pero , puede encontrar información útil sobre lo que está disponible para su función a través de thisy argumentos haciendo algo similar a lo que KennyTM señaló ( for (var propName in ____)) ya que eso le dirá lo que está disponible en varios objetos que se le proporcionan ( thisy argumentos; si está no estoy seguro de qué argumentos te dan, puedes averiguarlo a través de la argumentsvariable que está implícitamente definida para cada función).

Entonces, además de lo que esté dentro del alcance debido a dónde define su función, puede descubrir qué más está disponible por otros medios haciendo:

var n, arg, name;
alert("typeof this = " + typeof this);
for (name in this) {
    alert("this[" + name + "]=" + this[name]);
}
for (n = 0; n < arguments.length; ++n) {
    arg = arguments[n];
    alert("typeof arguments[" + n + "] = " + typeof arg);
    for (name in arg) {
        alert("arguments[" + n + "][" + name + "]=" + arg[name]);
    }
}

(Puede ampliar eso para obtener más información útil).

Sin embargo, en lugar de eso, probablemente usaría un depurador como las herramientas de desarrollo de Chrome (incluso si normalmente no usa Chrome para el desarrollo) o Firebug (incluso si normalmente no usa Firefox para el desarrollo) o Dragonfly en Opera o "Herramientas de desarrollo F12" en IE. Y lea los archivos JavaScript que le proporcionen. Y golpéalos en la cabeza para obtener los documentos adecuados. :-)

TJ Crowder
fuente
Como nota al margen, Google Chrome tiene un gran depurador comparable a Firebug (con un poco más de hielo en la parte superior también).
Giratorio
44
@Swivelgames: Oh, prefiero las herramientas de desarrollo de Chrome a Firefox + Firebug. Simplemente no estaban haciendo mucho en 2010. :-)
TJ Crowder
1
@tjcrowder De hecho! Noté que la marca de tiempo en la respuesta fue hace un tiempo :)
Swivel
98

Aunque todos responden " No " y sé que "No" es la respuesta correcta, pero si realmente necesita obtener variables locales de una función, hay una forma restringida.

Considere esta función:

var f = function() {
    var x = 0;
    console.log(x);
};

Puede convertir su función en una cadena:

var s = f + '';

Obtendrá fuente de función como una cadena

'function () {\nvar x = 0;\nconsole.log(x);\n}'

Ahora puede usar un analizador como esprima para analizar el código de función y encontrar declaraciones de variables locales.

var s = 'function () {\nvar x = 0;\nconsole.log(x);\n}';
s = s.slice(12); // to remove "function () "
var esprima = require('esprima');
var result = esprima.parse(s);

y encontrar objetos con:

obj.type == "VariableDeclaration"

en el resultado (he eliminado a console.log(x)continuación):

{
    "type": "Program",
    "body": [
        {
            "type": "VariableDeclaration",
            "declarations": [
                {
                    "type": "VariableDeclarator",
                    "id": {
                        "type": "Identifier",
                        "name": "x"
                    },
                    "init": {
                        "type": "Literal",
                        "value": 0,
                        "raw": "0"
                    }
                }
            ],
            "kind": "var"
        }
    ]
}

He probado esto en Chrome, Firefox y Node.

Pero el problema con este método es que solo tiene las variables definidas en la función misma. Por ejemplo para este:

var g = function() {
    var y = 0;
    var f = function() {
        var x = 0;
        console.log(x);
    };
}

solo tienes acceso a la xy no a la y . Pero aún puede usar cadenas de llamador (argumentos.callee.caller.caller.caller) en un bucle para encontrar variables locales de las funciones del llamador. Si tiene todos los nombres de variables locales, entonces tiene variables de alcance . Con los nombres de las variables tiene acceso a los valores con una evaluación simple.

iman
fuente
77
Excelente técnica aquí para abordar el problema original. Se merece un voto a favor.
profundización
44
Las variables de una persona que llama no son necesariamente variables de alcance. Además, las variables de alcance no están necesariamente en la cadena de llamadas. Las variables que se han cerrado pueden ser referenciadas por una función de cierre al llamarla desde una persona que llama que está fuera del alcance de definición / cierre (y cuyas variables no son accesibles desde dentro del cuerpo del cierre).
DDS
¿Podría explicar "una evaluación simple" clara? Intenté con el siguiente código, pero no pude obtener una variable atrapada en el alcance. function getCounter(start){ return function(){ start ++; return start; } } var counter = getCounter(1); var startInClosure = eval.call(counter, 'this.start;'); console.log(startInClosure);Se imprime undefinedmientras espero que sea 2.
Pylipala
@Pylipala La pregunta aquí es sobre las variables en el alcance que no obtienen el valor de las variables locales desde fuera del alcance. Eso no es posible, porque esta es la definición de variable local que no debería ser accesible desde el exterior. Con respecto a su código, se refiere al contexto, no al alcance, pero no puso inicio en el contexto (esto), por lo que no puede leerlo con this.start . Ver aquí: paste.ubuntu.com/11560449
iman
@iman Gracias por tu respuesta. También supongo que no es posible en el lado de Javascript, así que supongo que si es posible en el lado v8. Encontré el siguiente enlace de preguntas que describe mi problema con precisión. En él, Lasse Reichstein dice que no, pero mako-taco dice que sí. Intenté un código C ++ como enlace pero no sé cómo escribirlo.
Pylipala
32

Si y no. "No" en casi todas las situaciones. "Sí", pero solo de manera limitada, si desea verificar el alcance global. Tome el siguiente ejemplo:

var a = 1, b = 2, c = 3;

for ( var i in window ) {
    console.log(i, typeof window[i], window[i]);
}

Lo que genera, entre más de 150 cosas , lo siguiente:

getInterface function getInterface()
i string i // <- there it is!
c number 3
b number 2
a number 1 // <- and another
_firebug object Object firebug=1.4.5 element=div#_firebugConsole
"Firebug command line does not support '$0'"
"Firebug command line does not support '$1'"
_FirebugCommandLine object Object
hasDuplicate boolean false

Por lo tanto, es posible enumerar algunas variables en el alcance actual, pero no es confiable, sucinto, eficiente o de fácil acceso.

Una mejor pregunta es ¿por qué quieres saber qué variables están dentro del alcance?

Justin Johnson
fuente
2
Creo que la pregunta era más general y no se limitaba a JavaScript en el contexto de un navegador web.
Radu Simionescu
Simplemente cambie la palabra "ventana" por "global" si está usando el nodo ... o puede usar la palabra "esto" pero eso está prohibido en "usar estricto"
Ivan Castellanos
Agregué una línea para verificar hasOwnProperty. Por ejemplo: for ( var i in window ) { if (window.hasOwnProperty(i)) { console.log(i, window[i]); }}. Esto al menos reducirá las propiedades heredadas y sus variables estarán entre solo unas 50 propiedades más.
timctran
8
¿Por qué alguien no debería al menos querer verificar qué variables están dentro del alcance, durante la depuración y / o el desarrollo
Dexygen
Otra razón para querer saber qué variables están en el ámbito local es serializar un cierre.
Michael
29

En ECMAScript 6 es más o menos posible envolviendo el código dentro de una withdeclaración con un objeto proxy. Tenga en cuenta que requiere un modo no estricto y es una mala práctica.

function storeVars(target) {
  return new Proxy(target, {
    has(target, prop) { return true; },
    get(target, prop) { return (prop in target ? target : window)[prop]; }
  });
}
var vars = {}; // Outer variable, not stored.
with(storeVars(vars)) {
  var a = 1;   // Stored in vars
  var b = 2;   // Stored in vars
  (function() {
    var c = 3; // Inner variable, not stored.
  })();
}
console.log(vars);

El proxy afirma poseer todos los identificadores a los que se hace referencia en el interior with, por lo que las asignaciones de variables se almacenan en el destino. Para las búsquedas, el proxy recupera el valor del objetivo del proxy o del objeto global (no el ámbito principal). lety las constvariables no están incluidas.

Inspirado por esta respuesta de Bergi .

Oriol
fuente
1
Esto es sorprendentemente exitoso para una técnica tan simple.
Jonathan Eunice
WOW super power
pery mimon
16

No puedes

Las variables, los identificadores de las declaraciones de funciones y los argumentos para el código de función, están vinculados como propiedades del Objeto Variable , que no es accesible.

Ver también:

CMS
fuente
1
Es cierto, pero las variables dentro del alcance van más allá del objeto variable actual. Las variables dentro del alcance están determinadas por la cadena del alcance , que es una cadena que consiste (en el caso normal) de una serie de objetos variables y que termina con el objeto global (aunque la withinstrucción se puede usar para insertar otros objetos en la cadena).
TJ Crowder
+1 para enlaces a bclary.com. Me alegra decir que aparecerá una versión HTML de la especificación actualizada en los próximos meses en el sitio web de ECMA.
TJ Crowder
3
Solo una nota, ambos enlaces están rotos.
0xc0de
PUEDES - con análisis estático jsfiddle.net/mathheadinclouds/bvx1hpfn/11
mathheadinclouds
8

La forma más sencilla de obtener acceso a Vars en un ámbito particular

  1. Abra Herramientas de desarrollo> Recursos (en Chrome)
  2. Abra el archivo con una función que tenga acceso a ese ámbito (consejo cmd / ctrl + p para buscar el archivo)
  3. Establezca el punto de interrupción dentro de esa función y ejecute su código
  4. Cuando se detiene en su punto de interrupción, puede acceder al alcance var a través de la consola (o la ventana alcance var)

Nota: Desea hacer esto contra js no minificados.

La forma más sencilla de mostrar todos los Vars no privados

  1. Consola abierta (en Chrome)
  2. Tipo: this.window
  3. Pulsa Enter

Ahora verá un árbol de objetos que puede expandir con todos los objetos declarados.

Timothy Perez
fuente
7

Como todos notaron: no puedes. Pero puede crear un obj y asignar cada var que declare a ese obj. De esa manera, puede ver fácilmente sus variables:

var v = {}; //put everything here

var f = function(a, b){//do something
}; v.f = f; //make's easy to debug
var a = [1,2,3];
v.a = a;
var x = 'x';
v.x = x;  //so on...

console.log(v); //it's all there
rafaelcastrocouto
fuente
1
+1 gracias por un ejemplo genial, solo por completar lo mismo se puede ver también a través de console.log(window); jsfiddle.net/4x3Tx
Stano
5

Hice un violín implementando (esencialmente) las ideas anteriores esbozadas por iman. Así es como se ve cuando pasa el mouse sobre el segundo ipsum enreturn ipsum*ipsum - ...

ingrese la descripción de la imagen aquí

Las variables que están en el alcance se resaltan donde se declaran (con diferentes colores para diferentes ámbitos). El loremborde rojo es una variable sombreada (no está dentro del alcance, pero esté dentro del alcance si el otro lorem más abajo del árbol no estaría allí).

Estoy usando la biblioteca esprima para analizar JavaScript, y estraverse, escodegen, escope (bibliotecas de servicios además de esprima). El 'trabajo pesado' lo realizan todas esas bibliotecas (la más compleja es la propia esprima, por supuesto).

Cómo funciona

ast = esprima.parse(sourceString, {range: true, sourceType: 'script'});

hace el árbol de sintaxis abstracta. Luego,

analysis = escope.analyze(ast);

genera una estructura de datos compleja que encapsula información sobre todos los ámbitos del programa. El resto es reunir la información codificada en ese objeto de análisis (y el árbol de sintaxis abstracta en sí), y crear un esquema de coloración interactivo.

Entonces, la respuesta correcta en realidad no es "no", sino "sí, pero". El "pero" es importante: básicamente tienes que reescribir partes significativas del navegador Chrome (y sus herramientas de desarrollo) en JavaScript. JavaScript es un lenguaje completo de Turing, por lo que es posible, en principio. Lo que es imposible es hacer todo sin usar la totalidad de su código fuente (como una cadena) y luego hacer cosas muy complejas con eso.

Mathheadinclouds
fuente
3

Si solo desea inspeccionar las variables manualmente para ayudar a la depuración, simplemente inicie el depurador:

debugger;

Directamente en la consola del navegador.

David Meister
fuente