Nie jest to ani problem zakresu, ani nie jest to problem zamknięcia. Problem polega na zrozumieniu między deklaracjami a wyrażeniami .
Kod JavaScript, ponieważ nawet pierwsza wersja JavaScript firmy Netscape i pierwsza jej kopia Microsoftu jest przetwarzana w dwóch fazach:
Faza 1: kompilacja - w tej fazie kod jest kompilowany do postaci drzewa składni (i kodu bajtowego lub binarnego w zależności od silnika).
Faza 2: wykonanie - przeanalizowany kod jest następnie interpretowany.
Składnia deklaracji funkcji to:
function name (arguments) {code}
Argumenty są oczywiście opcjonalne (kod też jest opcjonalny, ale jaki to ma sens?).
Ale JavaScript umożliwia również tworzenie funkcji za pomocą wyrażeń . Składnia wyrażeń funkcji jest podobna do deklaracji funkcji, z wyjątkiem tego, że są one zapisywane w kontekście wyrażenia. A wyrażenia to:
- Cokolwiek na prawo od
=
znaku (lub :
na literałach obiektu).
- Wszystko w nawiasach
()
.
- Parametry funkcji (w rzeczywistości jest to już objęte 2).
Wyrażenia w przeciwieństwie do deklaracji są przetwarzane w fazie wykonywania, a nie w fazie kompilacji. Z tego powodu kolejność wyrażeń ma znaczenie.
Tak więc, aby wyjaśnić:
(function() {
setTimeout(someFunction, 10);
var someFunction = function() { alert('here1'); };
})();
Faza 1: kompilacja. Kompilator widzi, że zmienna someFunction
jest zdefiniowana, więc ją tworzy. Domyślnie wszystkie utworzone zmienne mają wartość undefined. Należy zauważyć, że kompilator nie może jeszcze przypisać wartości w tym momencie, ponieważ wartości mogą wymagać, aby interpreter wykonał jakiś kod, aby zwrócić wartość do przypisania. Na tym etapie jeszcze nie wykonujemy kodu.
Faza 2: wykonanie. Interpreter widzi, że chcesz przekazać zmienną someFunction
do setTimeout. I tak właśnie jest. Niestety aktualna wartość someFunction
jest nieokreślona.
(function() {
setTimeout(someFunction, 10);
function someFunction() { alert('here2'); }
})();
Faza 1: kompilacja. Kompilator widzi, że deklarujesz funkcję o nazwie someFunction i tworzy ją.
Faza 2: interpreter widzi, że chcesz przejść someFunction
do setTimeout. I tak właśnie jest. Bieżąca wartość someFunction
jest deklaracją skompilowanej funkcji.
(function() {
setTimeout(function() { someFunction(); }, 10);
var someFunction = function() { alert('here3'); };
})();
Faza 1: kompilacja. Kompilator widzi, że zadeklarowałeś zmienną someFunction
i tworzy ją. Jak poprzednio, jego wartość jest nieokreślona.
Faza 2: wykonanie. Interpreter przekazuje anonimową funkcję do setTimeout w celu wykonania później. W tej funkcji widzi, że używasz zmiennej, someFunction
więc tworzy zamknięcie zmiennej. W tym momencie wartość someFunction
jest nadal nieokreślona. Następnie widzi, jak przypisujesz funkcję do someFunction
. W tym momencie wartość someFunction
nie jest już nieokreślona. 1/100 sekundy później wyzwala setTimeout i wywoływana jest funkcja someFunction. Ponieważ jego wartość nie jest już nieokreślona, działa.
Przypadek 4 jest tak naprawdę inną wersją przypadku 2 z wrzuconym fragmentem przypadku 3. W punkcie someFunction
jest przekazywany do setTimeout, ponieważ już istnieje, ponieważ został zadeklarowany.
Dodatkowe wyjaśnienie:
Możesz się zastanawiać, dlaczego setTimeout(someFunction, 10)
nie tworzy zamknięcia między lokalną kopią someFunction a przekazaną do setTimeout. Odpowiedzią na to jest to, że argumenty funkcji w JavaScript są zawsze, zawsze przekazywane przez wartość, jeśli są liczbami lub łańcuchami, lub przez odniesienie do wszystkiego innego. Tak więc setTimeout w rzeczywistości nie pobiera zmiennej someFunction przekazanej do niej (co oznaczałoby utworzenie domknięcia), ale raczej pobiera tylko obiekt, do którego odnosi się someFunction (który w tym przypadku jest funkcją). Jest to najczęściej używany mechanizm w JavaScript do łamania domknięć (na przykład w pętlach).
Zakres JavaScript jest oparty na funkcjach, a nie ściśle leksykalnie. oznacza to, że
Somefunction1 jest zdefiniowany od początku otaczającej funkcji, ale jej zawartość jest niezdefiniowana, dopóki nie zostanie przypisana.
w drugim przykładzie przypisanie jest częścią deklaracji, więc „przesuwa się” na górę.
w trzecim przykładzie zmienna istnieje, gdy zdefiniowano anonimowe zamknięcie wewnętrzne, ale jest używana dopiero 10 sekund później, wtedy wartość została przypisana.
czwarty przykład ma zarówno drugi, jak i trzeci powód, dla którego warto działać
źródło
Ponieważ
someFunction1
nie został jeszcze przypisany w momencie wykonywania wywołaniasetTimeout()
.someFunction3 może wyglądać podobnej sprawie, ale ponieważ jesteś przechodzącą zawijanie funkcyjny
someFunction3()
abysetTimeout()
w tym przypadku wywołaniesomeFunction3()
nie jest oceniany dopiero później.źródło
someFunction2
czy nie został jeszcze przypisany, kiedy wywołaniesetTimeout()
jest wykonywane albo ...?function
słowa kluczowego nie jest dokładnie równoważne z przypisaniem funkcji anonimowej do zmiennej. Funkcje zadeklarowane jakofunction foo()
są „podnoszone” na początek bieżącego zakresu, podczas gdy przypisania zmiennych następują w miejscu, w którym są zapisywane.To brzmi jak podstawowy przypadek przestrzegania dobrej procedury, aby uniknąć kłopotów. Zadeklaruj zmienne i funkcje przed ich użyciem i zadeklaruj funkcje takie jak:
function name (arguments) {code}
Unikaj deklarowania ich za pomocą var. To jest po prostu niechlujne i prowadzi do problemów. Jeśli wpadniesz w nawyk deklarowania wszystkiego przed użyciem, większość problemów zniknie w wielkim pośpiechu. Deklarując zmienne, inicjowałbym je od razu prawidłową wartością, aby upewnić się, że żadna z nich nie jest niezdefiniowana. Mam również tendencję do dołączania kodu, który sprawdza prawidłowe wartości zmiennych globalnych, zanim funkcja ich użyje. Jest to dodatkowe zabezpieczenie przed błędami.
Szczegóły techniczne tego wszystkiego przypominają fizykę działania granatu ręcznego, kiedy się nim bawisz. Moja prosta rada to przede wszystkim nie bawić się granatami ręcznymi.
Niektóre proste deklaracje na początku kodu mogą rozwiązać większość tego rodzaju problemów, ale nadal może być konieczne pewne uporządkowanie kodu.
Dodatkowa uwaga:
Przeprowadziłem kilka eksperymentów i wygląda na to, że jeśli zadeklarujesz wszystkie swoje funkcje w sposób opisany tutaj, nie ma znaczenia, w jakiej kolejności się one znajdują. Jeśli funkcja A używa funkcji B, funkcja B nie musi być zadeklarowane przed funkcją A.
Dlatego najpierw zadeklaruj wszystkie funkcje, następnie zmienne globalne, a drugi kod umieść na końcu. Postępuj zgodnie z tymi praktycznymi zasadami, a nie popełnisz błędu. Najlepiej byłoby nawet umieścić swoje deklaracje w nagłówku strony internetowej, a inny kod w treści, aby zapewnić egzekwowanie tych zasad.
źródło