Uzyskiwanie wszystkich zmiennych w zakresie

194

Czy istnieje sposób na uzyskanie wszystkich zmiennych, które są obecnie objęte zakresem javascript?

Ian
źródło
Re odpowiedź na Camsoft: To zupełnie inne pytanie; Zaktualizowałem swoją odpowiedź, aby ją rozwiązać.
TJ Crowder,
3
To tylko ogólne pytanie, ponieważ bardziej konkretny nie pomoże, ponieważ pracuję z niejasnym API z kiepską dokumentacją.
Ian
Masz na myśli zmienne globalne! Można wyświetlić wyliczalne zmienne globalne za pomocą for (v in this) alert(v);. Jednak nie wszystkie globale są policzalne i nie znam żadnego standardowego sposobu na uzyskanie listy niepoliczalnych.
Jason Orendorff,
2
@Jason - Nie, pytanie jest jasne. Wewnątrz funkcji zmienne w zakresie obejmie zmiennych globalnych, this, arguments, parametry i wszystkie zmienne zdefiniowane w załączając zakresów.
Tim Down
2
Dlatego tęsknię za tabelami symboli Perla. Czy planujesz dodać to do przyszłej wersji Javascript?
Dexygen

Odpowiedzi:

84

Nie. Zmienne „w zakresie” są określane przez „łańcuch zasięgu”, który nie jest dostępny programowo.

Aby uzyskać szczegółowe informacje (całkiem sporo), sprawdź specyfikację ECMAScript (JavaScript). Oto link do oficjalnej strony, na której można pobrać specyfikację kanoniczną (PDF), a tutaj do oficjalnej, możliwej do połączenia wersji HTML.

Zaktualizuj na podstawie Twojego komentarza do Camsoft

Zmienne w zakresie funkcji zdarzenia zależą od tego, gdzie definiujesz funkcję zdarzenia, a nie jak ją nazywają. Ale możesz znaleźć przydatne informacje o tym, co jest dostępne dla twojej funkcji thisi argumenty, robiąc coś zgodnie z tym, co zauważył KennyTM ( for (var propName in ____)), ponieważ powie ci to, co jest dostępne na różnych obiektach dostarczonych ci ( thisi argumentach; jeśli jesteś nie jestem pewien, jakie argumenty ci podają, możesz dowiedzieć się przez argumentszmienną, która jest domyślnie zdefiniowana dla każdej funkcji).

Oprócz tego, co jest poza zakresem, ze względu na to, gdzie definiujesz swoją funkcję, możesz dowiedzieć się, co jeszcze jest dostępne innymi sposobami, wykonując:

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

(Możesz to rozwinąć, aby uzyskać bardziej przydatne informacje).

Zamiast tego jednak prawdopodobnie użyłbym debuggera, takiego jak narzędzia programistyczne Chrome (nawet jeśli normalnie nie używasz Chrome do programowania) lub Firebug (nawet jeśli normalnie nie używasz Firefox do programowania) lub Dragonfly w Operze lub „F12 Developer Tools” w przeglądarce IE. I przeczytaj wszystkie dostarczone przez Ciebie pliki JavaScript. I pobij ich nad głowę, aby uzyskać odpowiednie dokumenty. :-)

TJ Crowder
źródło
Na marginesie, Google Chrome ma świetny debugger porównywalny z Firebug (z nieco więcej lukru na górze).
Obrotowe
4
@Swivelgames: Och, zdecydowanie wolę narzędzia deweloperskie Chrome niż Firefox + Firebug. Po prostu nie było ich wiele w 2010 roku. :-)
TJ Crowder
1
@tjcrowder Rzeczywiście! Zauważyłem znacznik czasu w odpowiedzi jakiś czas temu :)
Swivel
98

Chociaż wszyscy odpowiadają „ Nie ” i wiem, że „Nie” jest właściwą odpowiedzią, ale jeśli naprawdę potrzebujesz uzyskać lokalne zmienne funkcji, istnieje ograniczony sposób.

Rozważ tę funkcję:

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

Możesz przekonwertować swoją funkcję na ciąg:

var s = f + '';

Otrzymasz źródło funkcji jako ciąg

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

Teraz możesz używać analizatora składni, takiego jak esprima, do analizowania kodu funkcji i znajdowania deklaracji zmiennych lokalnych.

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

i znajdź obiekty za pomocą:

obj.type == "VariableDeclaration"

w wyniku (usunąłem console.log(x)poniżej):

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

Przetestowałem to w Chrome, Firefox i Node.

Ale problem tej metody jest to, że po prostu mają zmienne zdefiniowane w samej funkcji. Na przykład dla tego:

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

masz tylko dostęp do x, a nie y . Ale nadal możesz używać łańcuchów wywołujących (arguments.callee.caller.caller.caller) w pętli, aby znaleźć lokalne zmienne funkcji wywołujących. Jeśli masz wszystkie lokalne nazwy zmiennych, więc masz zmienne zakresu . Dzięki nazwom zmiennych masz dostęp do wartości za pomocą prostej ewaluacji.

iman
źródło
7
Doskonała technika rozwiązania pierwotnego problemu. Zasługuje na uznanie.
deepelement
4
Zmienne wywołującego niekoniecznie są zmiennymi zakresu. Ponadto zmienne zakresu niekoniecznie są w łańcuchu połączeń. Zmienne, które zostały zamknięte, mogą być przywoływane przez funkcję zamknięcia po wywołaniu jej z obiektu wywołującego, który jest poza zakresem definicji / zamknięcia (i którego zmienne w ogóle nie są dostępne z poziomu ciała zamknięcia).
DDS
Czy mógłbyś wyjaśnić „zwykłą ewaluację” jasne? Próbowałem z poniższym kodem, ale nie mogłem uwięzić zmiennej w zakresie. function getCounter(start){ return function(){ start ++; return start; } } var counter = getCounter(1); var startInClosure = eval.call(counter, 'this.start;'); console.log(startInClosure);Drukuje, undefinedpodczas gdy spodziewam się, że powinno być 2.
Pylipala
@Pylipala Pytanie dotyczy tutaj zmiennych, które nie uzyskują wartości zmiennych lokalnych spoza zakresu. Nie jest to możliwe, ponieważ jest to definicja zmiennej lokalnej, która nie powinna być dostępna z zewnątrz. Jeśli chodzi o kod, masz na myśli kontekst nie zakres, ale nie umieścić rozpocząć w kontekście (tym), więc nie można go odczytać z this.start . Zobacz tutaj: paste.ubuntu.com/11560449
iman
@iman Dzięki za odpowiedź. Myślę też, że nie jest to możliwe po stronie Javascript, więc sądzę, że jest to możliwe po stronie v8. Znalazłem poniższy link do pytania, który dokładnie opisuje mój problem. W tym Lasse Reichstein mówi nie, ale mako-taco mówi tak. Próbowałem trochę kodu C ++ jako linku, ale nie wiem, jak go napisać.
Pylipala
32

Tak i nie. „Nie” w prawie każdej sytuacji. „Tak”, ale tylko w ograniczony sposób, jeśli chcesz sprawdzić zasięg globalny. Weź następujący przykład:

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

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

Które wyniki, spośród ponad 150 innych rzeczy , następujące:

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

Można więc wymienić niektóre zmienne w bieżącym zakresie, ale nie jest to wiarygodne, zwięzłe, wydajne ani łatwo dostępne.

Lepszym pytaniem jest to, dlaczego chcesz wiedzieć, jakie zmienne są objęte zakresem?

Justin Johnson
źródło
2
Myślę, że pytanie było bardziej ogólne i nie ograniczało się do javascript w kontekście przeglądarki internetowej.
Radu Simionescu,
Po prostu zmień słowo „okno” na „globalny”, jeśli używasz węzła ... lub możesz użyć słowa „to”, ale jest to zabronione pod „używaj ścisłego”
Ivan Castellanos
Dodałem linię sprawdzającą hasOwnProperty. Na przykład: for ( var i in window ) { if (window.hasOwnProperty(i)) { console.log(i, window[i]); }}. Spowoduje to co najmniej zmniejszenie odziedziczonych właściwości, a twoje zmienne będą znajdować się tylko wśród około 50 innych właściwości.
timctran
8
Dlaczego ktoś nie powinien przynajmniej chcieć sprawdzić, jakie zmienne są objęte zakresem podczas debugowania i / lub programowania.
Dexygen
Innym powodem, dla którego chcemy wiedzieć, jakie zmienne są w zasięgu lokalnym, jest serializacja zamknięcia.
Michael
29

W ECMAScript 6 jest to mniej więcej możliwe poprzez zawinięcie kodu w withinstrukcję za pomocą obiektu proxy. Zauważ, że wymaga trybu nie ścisłego i jest to zła praktyka.

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

Serwer proxy twierdzi, że jest właścicielem wszystkich identyfikatorów, do których istnieją odniesienia with, więc przypisania zmiennych są przechowywane w celu. W przypadku wyszukiwań serwer proxy pobiera wartość z obiektu docelowego proxy lub obiektu globalnego (nie zakresu nadrzędnego). leti constzmienne nie są uwzględnione.

Zainspirowany tą odpowiedź przez Bergi .

Oriol
źródło
1
Jest to zaskakująco skuteczne w przypadku tak prostej techniki.
Jonathan Eunice,
WOW super power
pery mimon
16

Nie możesz

Zmienne, identyfikatory deklaracji funkcji i argumenty dla kodu funkcji są powiązane jako właściwości obiektu zmiennej , co jest niedostępne.

Zobacz też:

CMS
źródło
1
To prawda, ale zmienne w zakresie wykraczają poza tylko bieżący obiekt zmiennej. Zmienne wewnątrz zakresu są określane przez łańcuch zasięgu , który jest łańcuchem składającym się (w normalnym przypadku) z szeregu zmiennych obiektów i kończącym się na obiekcie globalnym (chociaż withinstrukcja może służyć do wstawiania innych obiektów w łańcuchu).
TJ Crowder,
+1 za linki do bclary.com. Z przyjemnością mogę powiedzieć, że wersja HTML aktualnej specyfikacji pojawi się w ciągu najbliższych kilku miesięcy na stronie internetowej ECMA.
TJ Crowder,
3
Tylko uwaga, oba linki są zepsute.
0xc0de,
MOŻESZ - z analizą statyczną jsfiddle.net/mathheadinclouds/bvx1hpfn/11
mathheadinclouds
8

Najprostszy sposób na uzyskanie dostępu do Vars w określonym zakresie

  1. Otwórz Narzędzia dla programistów> Zasoby (w Chrome)
  2. Otwórz plik za pomocą funkcji, która ma dostęp do tego zakresu (tip cmd / ctrl + p, aby znaleźć plik)
  3. Ustaw punkt przerwania wewnątrz tej funkcji i uruchom swój kod
  4. Kiedy zatrzyma się w punkcie przerwania, możesz uzyskać dostęp do var var za pośrednictwem konsoli (lub okna var var)

Uwaga: Chcesz to zrobić przeciwko niezminimalizowanym js.

Najprostszy sposób na pokazanie wszystkich nieprywatnych zmiennych

  1. Otwórz konsolę (w Chrome)
  2. Wpisz: this.window
  3. Wciśnij Enter

Teraz zobaczysz drzewo obiektów, które możesz rozwinąć o wszystkie zadeklarowane obiekty.

Timothy Perez
źródło
7

Jak wszyscy zauważyli: nie możesz. Ale możesz utworzyć obiekt i przypisać do niego wszystkie deklarowane zmienne. W ten sposób możesz łatwo sprawdzić swoje zmienne:

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
źródło
1
+1 dzięki za fajny przykład, tylko za ukończenie to samo można zobaczyć również przez console.log(window); jsfiddle.net/4x3Tx
Stano
5

Zrobiłem skrzypce, wdrażając (zasadniczo) powyżej pomysłów nakreślonych przez iman. Oto jak to wygląda po najechaniu myszką na drugie ipsum wreturn ipsum*ipsum - ...

wprowadź opis zdjęcia tutaj

Zmienne wchodzące w zakres są podświetlone tam, gdzie zostały zadeklarowane (różnymi kolorami dla różnych zakresów). Thelorem czerwoną ramką jest zaciemnioną zmienną (nie w zakresie, ale należy do zakresu, jeśli nie byłoby tam drugiej lorem dalej w dół drzewa).

Korzystam z biblioteki esprima do analizowania JavaScript i estraverse, escodegen, escope (biblioteki narzędziowe na górze esprima). „Ciężkie podnoszenie” jest wykonywane przez te biblioteki (oczywiście najbardziej złożona jest sama esprima).

Jak to działa

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

tworzy abstrakcyjne drzewo składniowe. Następnie,

analysis = escope.analyze(ast);

generuje złożoną strukturę danych enkapsulującą informacje o wszystkich zakresach w programie. Reszta zbiera informacje zakodowane w tym obiekcie analitycznym (i samym abstrakcyjnym drzewie składni) i tworzy z niego interaktywny schemat kolorowania.

Tak więc poprawna odpowiedź to w rzeczywistości nie „nie”, ale „tak, ale”. „Ale” jest duże: w zasadzie musisz przepisać znaczące części przeglądarki Chrome (i to devtools) w JavaScript. JavaScript jest kompletnym językiem Turinga, więc oczywiście jest to w zasadzie możliwe. Niemożliwe jest zrobienie wszystkiego bez użycia całego kodu źródłowego (jako łańcucha), a następnie zrobienie z nim bardzo skomplikowanych rzeczy.

mathheadinclouds
źródło
3

Jeśli chcesz tylko ręcznie sprawdzić zmienne, aby pomóc w debugowaniu, po prostu uruchom debugger:

debugger;

Prosto do konsoli przeglądarki.

David Meister
źródło