2015-02-07 13 views
99

Z tym kodem:Dlaczego domyślna zmienna lokalna Chrome debuggera jest niezdefiniowana?

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

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

otrzymuję ten nieoczekiwany rezultat:

enter image description here

Kiedy zmienić kod:

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

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

mogę uzyskać oczekiwany wynik:

enter image description here

Ponadto, jeśli w wewnętrznej funkcji istnieje połączenie z eval, mogę uzyskać dostęp do mojej zmiennej tak, jak chcę (nie ma znaczenia, co przekazuję do eval).

W międzyczasie narzędzia programistyczne Firefox dają oczekiwane zachowanie w obu okolicznościach.

Co jest w Chrome, że debugger zachowuje się mniej wygodnie niż Firefox? Obserwowałem to zachowanie przez jakiś czas, aż do wersji 41.0.2272.43 beta (wersja 64-bitowa).

Czy mechanizm javascript przeglądarki Chrome "spłaszcza" funkcje, gdy może?

Co ciekawe, jeśli dodam drugą zmienną, która jest odwołanie w funkcji wewnętrznej, zmienna x jest nadal nieokreślone.

Rozumiem, że często występują dziwactwa z definicją zakresu i zmiennej podczas korzystania z interaktywnego debuggera, ale wydaje mi się, że w oparciu o specyfikację językową powinno istnieć "najlepsze" rozwiązanie tych dziwactw. Jestem więc bardzo ciekawy, czy wynika to z dalszej optymalizacji Chrome niż Firefox. A także, czy te optymalizacje można łatwo wyłączyć podczas programowania (może powinny one zostać wyłączone, gdy narzędzia programistyczne są otwarte?).

Mogę również odtworzyć to z wartościami granicznymi, jak również z instrukcją debugger.

+2

Może to przyzwyczaić un zmienne z twojej drogi dla ciebie ... – dandavis

+0

markle976 wydaje się mówić, że linia 'debugger;' nie jest tak naprawdę nazywana od m wewnątrz 'bar'. Tak więc spójrz na ślad stosu, gdy zatrzymuje się on w debugerze: czy funkcja 'bar' jest wymieniona w stosie? Jeśli mam rację, wtedy stacktrace powinien powiedzieć, że jest zatrzymany w linii 5, w linii 7, w linii 9. –

+0

Nie sądzę, że ma to coś wspólnego z funkcjami spłaszczania V8. Myślę, że to tylko dziwactwo; Nie wiem, czy mógłbym to nazwać błędem. Myślę, że odpowiedź Davida poniżej ma sens. – markle976

Odpowiedz

98

Znalazłem V8 issue report który jest precyzyjny ly o tym, o co pytasz.

Teraz, aby podsumować to, co zostało powiedziane w tym raporcie emisyjnej ... v8 może przechowywać zmienne, które są lokalne dla funkcji na stosie lub w „kontekst” obiektu, który mieszka na stercie. Przydziela lokalne zmienne na stosie, o ile funkcja nie zawiera żadnej wewnętrznej funkcji, która się do nich odnosi. Jest to optymalizacja. Jeśli funkcja wewnętrzna odnosi się do zmiennej lokalnej, zmienna ta zostanie umieszczona w obiekcie kontekstu (tj. Na stercie zamiast na stosie). Przypadek eval jest wyjątkowy: jeśli w ogóle wywoływana jest przez funkcję wewnętrzną, wszystkie zmienne lokalne są umieszczane w obiekcie kontekstu.

Powód obiektu kontekstowego polega na tym, że można zwrócić funkcję wewnętrzną od zewnętrznej, a następnie stos, który istniał podczas działania funkcji zewnętrznej, nie będzie już dostępny. Więc wszystko, do czego ta funkcja wewnętrzna ma dostęp, musi przetrwać zewnętrzną funkcję i żyć na stercie, a nie na stosie.

Debugger nie może sprawdzić tych zmiennych, które znajdują się na stosie. Jeśli chodzi o problemy napotkane w debugowania jeden członek Projektu says:

Jedynym rozwiązaniem mogłoby myślę o to, że gdy DevTools jest, byśmy deopt cały kod i skompilować z przymusowej alokacji kontekstowego. To drastycznie cofnęłoby wydajność z włączonymi devtools.

Oto przykład "jeśli jakaś funkcja wewnętrzna odnosi się do zmiennej, umieść ją w obiekcie kontekstu". Jeśli uruchomisz to, będziesz mógł uzyskać dostęp do x na oświadczeniu debugger, mimo że x jest używany tylko w funkcji foo, , która nigdy nie jest nazywana!

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

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

    function bar() { 
    debugger; 
    }; 

    bar(); 
} 
baz(); 
+6

Czy wymyśliłeś sposób na zdeformowanie kodu? Chciałbym użyć debuggera jako REPL i kodu, a następnie przenieść kod do moich własnych plików. Ale często nie jest to możliwe, ponieważ zmienne, które powinny tam być, nie są dostępne. Prosta ewaluta tego nie zrobi. Słyszę nieskończoną pętlę for. –

+0

Podczas debugowania faktycznie nie napotkam tego problemu, więc nie szukałem sposobów na deoptowanie kodu. – Louis

+4

Ostatni komentarz z wydania mówi: * Umieszczenie V8 w trybie, w którym wszystko jest przymusowo przydzielone do kontekstu jest możliwe, ale nie jestem pewien jak/kiedy to wywołać poprzez Devtools UI * Dla celów debugowania, czasami chciałbym aby to zrobić. Jak mogę wymusić taki tryb? – Suma

0

Podejrzewam, że ma to związek z podnoszeniem zmiennych i funkcji. JavaScript łączy wszystkie deklaracje zmiennych i funkcji w górnej części funkcji są zdefiniowane w Więcej informacji tutaj. http://jamesallardice.com/explaining-function-and-variable-hoisting-in-javascript/

Założę się, że Chrome jest wywołanie punkt zerwania z zmienna jest niedostępna do zakresu, ponieważ w nie ma nic innego funkcjonować. To wydaje się działać:

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

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

Jak to robi:

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

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

Nadzieja ta i/lub link powyżej pomaga. To jest mój ulubiony rodzaj pytań SO, BTW :)

+0

Dzięki! :) Zastanawiam się, co FF robi inaczej. Z mojej perspektywy jako programisty, doświadczenie FF jest obiektywnie lepsze ... –

+2

"dzwonienie do punktu przerw w czasie lekcji" Wątpię. Nie do tego służą limity. I nie rozumiem, dlaczego nieobecność innych rzeczy w funkcji ma znaczenie. Powiedziawszy to, jeśli jest to coś w rodzaju nodejs, to punkty przerwania mogą być bardzo błędne. –

6

Zauważyłem to również w nodejs.Wierzę (i przyznaję, że to tylko przypuszczenie), że gdy kod zostanie skompilowany, jeśli x nie pojawi się w bar, nie spowoduje to, że x będzie dostępny w zakresie bar. Prawdopodobnie czyni to nieco bardziej wydajnym; problem polega na tym, że ktoś zapomniał (lub nie przejmował się), że nawet jeśli nie ma x w bar, możesz zdecydować się na uruchomienie debuggera, a tym samym nadal potrzebujesz dostępu do x od wewnątrz bar.

+2

Dzięki. Zasadniczo chcę być w stanie wyjaśnić to początkującym javascript lepiej niż "Debugger leży". –

+0

@GabeKopley: Technicznie debugger nie kłamie. Jeśli zmienna nie jest przywoływana, nie jest ona technicznie zamknięta. W związku z tym nie ma potrzeby, aby tłumacz tworzył zamknięcie. – slebetman

+2

Nie o to chodzi. Podczas korzystania z debuggera często znajdowałem się w sytuacji, w której chciałem poznać wartość zmiennej w zewnętrznym zasięgu, ale nie mogłem z tego powodu. I w bardziej filozoficznej tonie, powiedziałbym, że debugger kłamie. To, czy zmienna istnieje w wewnętrznym zasięgu, nie powinno zależeć od tego, czy faktycznie jest używane, czy też istnieje niepowiązane polecenie 'eval'. Jeśli zmienna jest zadeklarowana, powinna być dostępna. –

2

Wow, naprawdę interesujące!

Jak wspomnieli inni, wydaje się, że jest to związane z scope, ale dokładniej, związane z debugger scope. Kiedy wstrzykiwany skrypt jest oceniany w narzędziach programistycznych, wydaje się, że określa on ScopeChain, co powoduje pewne dziwactwo (ponieważ jest powiązane z zakresem inspektora/debuggera). Odmianą tego, co napisali to:

(EDIT - faktycznie, to o tym wspomnieć w swoim pierwotnym pytaniu Yikes, mój zły!)

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

dla ambitnych i/lub ciekawy, zakresu (heh) na źródło, aby zobaczyć, co się dzieje:

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

+0

Dzięki za linki do źródła! –

9

Podobnie jak @Louis powiedział, że przyczyną są optymalizacje w wersji 8. można przemierzać stosu wywołań do klatki, gdzie zmienna ta jest widoczna:

call1 call2

Albo zastąpić debugger z

eval('debugger'); 

eval będzie deopt aktualny klocek

+0

Prawie świetnie! Zatrzymuje się w module VM (żółtym) z "debuggerem" zawartości, a kontekst jest rzeczywiście dostępny. Jeśli zwiększysz stos o jeden poziom do kodu, który faktycznie próbujesz debugować, wrócisz do braku dostępu do kontekstu. Jest to po prostu trochę niezgrabne, nie mogąc spojrzeć na kod, który debugujesz, uzyskując dostęp do ukrytych zmiennych zamykających. Jednak przegłosuję, ponieważ oszczędza mi to konieczności dodawania kodu, który nie jest oczywisty do debugowania, i daje mi dostęp do całego kontekstu bez deoptymalizacji całej aplikacji. – Sigfried

+0

Oh ... jest jeszcze bardziej zagłuszany niż użycie żółtego okna 'eval'ed, aby uzyskać dostęp do kontekstu: nie możesz przejść przez kod (chyba że umieścisz' eval ('debugger') 'pomiędzy wszystkimi liniami chcesz przejść.) – Sigfried