Obtendo todas as variáveis ​​no escopo

194

Existe uma maneira de obter todas as variáveis ​​que estão atualmente no escopo em javascript?

Ian
fonte
Re sua resposta à Camsoft: Essa é uma pergunta totalmente diferente; Atualizei minha resposta para resolvê-lo.
TJ Crowder
3
É apenas uma pergunta geral, sendo mais específico não ajudará muito, pois estou trabalhando com uma API obscura e com pouca documentação.
Ian
Você quer dizer variáveis ​​globais! Você pode exibir as inúmeras variáveis ​​globais usando for (v in this) alert(v);. Porém, nem todas as globais são enumeráveis, e eu não conheço nenhuma maneira padrão de obter uma lista das não enumeráveis.
Jason Orendorff
2
@ Jason - Não, a questão é clara. Dentro de uma função das variáveis no escopo incluirá variáveis globais, this, arguments, parâmetros e todas as variáveis definidas no abrangendo os escopos.
Tim Down
2
É por isso que sinto falta das tabelas de símbolos do Perl. Algum plano para adicionar isso a uma versão futura do Javascript?
Dexygen 27/09/15

Respostas:

84

Não. As variáveis ​​"no escopo" são determinadas pela "cadeia de escopo", que não é acessível programaticamente.

Para detalhes (muitos deles), consulte a especificação ECMAScript (JavaScript). Aqui está um link para a página oficial, onde você pode baixar a especificação canônica (um PDF), e aqui está um para a versão HTML linkável oficial.

Atualização com base no seu comentário para Camsoft

As variáveis no escopo da sua função de evento são determinadas por onde você define sua função de evento, não como elas a chamam.Porém , você pode encontrar informações úteis sobre o que está disponível para sua função por meio de thisargumentos, fazendo algo como o KennyTM apontou ( for (var propName in ____)), pois isso informará o que está disponível em vários objetos fornecidos a você ( thise argumentos; se você estiver sem saber quais argumentos eles fornecem, você pode descobrir através da argumentsvariável definida implicitamente para cada função).

Portanto, além do que estiver no escopo por causa de onde você define sua função, você pode descobrir o que mais está disponível por outros meios, fazendo:

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]);
    }
}

(Você pode expandir isso para obter informações mais úteis.)

Em vez disso, eu provavelmente usaria um depurador como as ferramentas de desenvolvimento do Chrome (mesmo que você normalmente não use o Chrome para desenvolvimento) ou o Firebug (mesmo que você normalmente não use o Firefox para o desenvolvimento) ou o Dragonfly on Opera ou "F12 Developer Tools" no IE. E leia os arquivos JavaScript que eles fornecem. E bata na cabeça deles para documentos adequados. :-)

TJ Crowder
fonte
Como uma observação lateral, o Google Chrome possui um ótimo depurador comparável ao Firebug (com um pouco mais de cobertura também).
giratória
4
@Swivelgames: Ah, eu prefiro Dev Tools do Chrome ao Firefox + Firebug. Eles simplesmente não estavam conseguindo muita coisa em 2010. :-)
TJ Crowder
1
@tjcrowder De fato! Notei a hora em que a resposta era um tempo atrás :)
Swivel
98

Embora todos respondam " Não " e eu saiba que "Não" é a resposta certa, mas se você realmente precisar obter variáveis ​​locais de uma função, existe uma maneira restrita.

Considere esta função:

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

Você pode converter sua função em uma string:

var s = f + '';

Você obterá a fonte da função como uma string

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

Agora você pode usar um analisador como o esprima para analisar o código de função e encontrar declarações de variáveis ​​locais.

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

e encontre objetos com:

obj.type == "VariableDeclaration"

no resultado (removi console.log(x)abaixo):

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

Eu testei isso no Chrome, Firefox e Node.

Mas o problema com esse método é que você apenas tem as variáveis ​​definidas na própria função. Por exemplo, para este:

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

você apenas tem acesso ao x e não ao y . Mas ainda assim você pode usar cadeias de chamador (argumentos.callee.caller.caller.caller) em um loop para encontrar variáveis ​​locais das funções de chamador. Se você tiver todos os nomes de variáveis ​​locais, terá variáveis ​​de escopo . Com os nomes das variáveis, você tem acesso aos valores com uma avaliação simples.

iman
fonte
7
Excelente técnica aqui para resolver o problema original. Merece um voto positivo.
deepelement
4
As variáveis ​​de um chamador não são necessariamente variáveis ​​de escopo. Além disso, as variáveis ​​de escopo não estão necessariamente na cadeia de chamadas. As variáveis ​​que foram encerradas podem ser referenciadas por uma função de fechamento ao chamá-la de um chamador que está fora do escopo de definição / fechamento (e cujas variáveis ​​não são de todo acessíveis no corpo do fechamento).
DDS
Você poderia explicar "uma avaliação simples" clara? Eu tentei com o código abaixo, mas não foi possível obter a variável presa no escopo. function getCounter(start){ return function(){ start ++; return start; } } var counter = getCounter(1); var startInClosure = eval.call(counter, 'this.start;'); console.log(startInClosure);Ela imprime undefinedenquanto eu esperava que deve ser 2.
Pylipala
@Pylipala A questão aqui é sobre variáveis ​​no escopo que não obtêm valor de variáveis ​​locais fora do escopo. Isso não é possível, porque esta é a definição de variável local que não deve ser acessível de fora. Em relação ao seu código, você quer dizer contexto, não escopo, mas você não colocou start no contexto (this), portanto não pode lê-lo com this.start . Veja aqui: paste.ubuntu.com/11560449
iman
@ iman Obrigado pela sua resposta. Também acho que não é possível no lado do Javascript, então acho que se é possível no lado da v8. Encontrei abaixo o link da pergunta que descreve meu problema com precisão. Nele, Lasse Reichstein diz não, mas mako-taco diz que sim. Eu tentei algum código C ++ como link, mas não sei como escrevê-lo.
Pylipala
32

Sim e não. "Não" em quase todas as situações. "Sim", mas apenas de maneira limitada, se você quiser verificar o escopo global. Veja o seguinte exemplo:

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

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

Que gera, entre mais de 150 outras coisas , o seguinte:

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

Portanto, é possível listar algumas variáveis ​​no escopo atual, mas não é confiável, sucinto, eficiente ou facilmente acessível.

Uma pergunta melhor é por que você quer saber quais variáveis ​​estão no escopo?

Justin Johnson
fonte
2
Eu acho que a pergunta era mais geral e não restrita ao javascript no contexto de um navegador da web.
Radu Simionescu
Basta alterar a palavra "janela" para "global" se você estiver usando nó ... ou você pode usar a palavra "presente", mas que é proibido em "use strict"
Ivan Castellanos
Eu adicionei uma verificação de linha para hasOwnProperty. Por exemplo: for ( var i in window ) { if (window.hasOwnProperty(i)) { console.log(i, window[i]); }}. Isso reduzirá pelo menos as propriedades herdadas e suas variáveis ​​estarão entre apenas cerca de 50 outras propriedades.
timctran
8
Por que alguém não deveria pelo menos querer verificar quais variáveis ​​estão no escopo, durante a depuração e / ou para fins de desenvolvimento.
Dexygen 27/09/15
Outro motivo para querer saber quais variáveis ​​estão no escopo local é serializar um fechamento.
Michael
29

No ECMAScript 6, é mais ou menos possível envolvendo o código dentro de uma withinstrução com um objeto proxy. Observe que isso requer um modo não estrito e é uma prática ruim.

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

O proxy afirma possuir todos os identificadores mencionados dentro with, para que as atribuições de variáveis ​​sejam armazenadas no destino. Para pesquisas, o proxy recupera o valor do destino do proxy ou do objeto global (não no escopo pai). lete constvariáveis ​​não estão incluídas.

Inspirado por esta resposta de Bergi .

Oriol
fonte
1
Isso é surpreendentemente bem-sucedido para uma técnica tão simples.
Jonathan Eunice #
WOW super power
pery mimon
16

Você não pode.

Variáveis, identificadores de declarações de funções e argumentos para código de função, são vinculados como propriedades do objeto variável , o que não é acessível.

Veja também:

CMS
fonte
1
É verdade, mas as variáveis ​​dentro do escopo vão além do objeto de variável atual. Variáveis ​​no escopo são determinadas pela cadeia de escopo , que é uma cadeia que consiste (no caso normal) de uma série de objetos variáveis ​​e termina com o objeto global (embora a withinstrução possa ser usada para inserir outros objetos na cadeia).
TJ Crowder
+1 para links para bclary.com. Uma versão HTML das especificações atualizadas aparecerá nos próximos dois meses no site da ECMA, tenho o prazer de dizer.
TJ Crowder
3
Apenas uma nota, ambos os links estão quebrados.
0xc0de 13/09/16
Você pode - com a análise estática jsfiddle.net/mathheadinclouds/bvx1hpfn/11
mathheadinclouds
8

A maneira mais simples de obter acesso a Vars em um escopo específico

  1. Abra Ferramentas do desenvolvedor> Recursos (no Chrome)
  2. Abrir arquivo com uma função que tenha acesso a esse escopo (dica cmd / ctrl + p para localizar o arquivo)
  3. Defina o ponto de interrupção dentro dessa função e execute seu código
  4. Quando ele pára no seu ponto de interrupção, você pode acessar a var do escopo através do console (ou da janela var do escopo)

Nota: Você deseja fazer isso em js não minificados.

A maneira mais simples de mostrar todos os Vars não privados

  1. Console aberto (no Chrome)
  2. Digite: this.window
  3. Pressione Enter

Agora você verá uma árvore de objetos que pode ser expandida com todos os objetos declarados.

Timothy Perez
fonte
7

Como todo mundo percebeu: você não pode. Mas você pode criar um objeto e atribuir todos os var que você declara a esse objeto. Dessa forma, você pode facilmente verificar seus vars:

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
fonte
1
+1 obrigado por exemplo legal, apenas para conclusão do mesmo pode ser visto também através console.log(window); jsfiddle.net/4x3Tx
Stano
5

Eu fiz um violino implementando (essencialmente) as idéias delineadas por iman. Aqui está como fica quando você passa o mouse sobre o segundo ipsumreturn ipsum*ipsum - ...

insira a descrição da imagem aqui

As variáveis ​​que estão no escopo são destacadas onde são declaradas (com cores diferentes para diferentes escopos). olorem borda com vermelho é uma variável sombreada (não está no escopo, mas esteja no escopo se o outro lorem mais abaixo da árvore não estiver lá.)

Estou usando a biblioteca esprima para analisar o JavaScript e estraverse, escodegen, escope (bibliotecas de utilidade em cima da esprima.) O 'trabalho pesado' é feito por todas essas bibliotecas (a mais complexa é a própria esprima, é claro).

Como funciona

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

cria a árvore de sintaxe abstrata. Então,

analysis = escope.analyze(ast);

gera uma estrutura de dados complexa que encapsula informações sobre todos os escopos no programa. O restante está reunindo as informações codificadas nesse objeto de análise (e a própria árvore de sintaxe abstrata) e criando um esquema de cores interativo.

Portanto, a resposta correta não é realmente "não", mas "sim, mas". O "mas" é grande: você basicamente tem que reescrever partes significativas do navegador chrome (e suas devtools) em JavaScript. JavaScript é uma linguagem completa de Turing, portanto é claro que isso é possível, em princípio. O que é impossível é fazer a coisa toda sem usar todo o seu código-fonte (como uma string) e depois fazer coisas altamente complexas com isso.

mathheadinclouds
fonte
3

Se você quiser apenas inspecionar as variáveis ​​manualmente para ajudar na depuração, inicie o depurador:

debugger;

Diretamente no console do navegador.

David Meister
fonte