Dlaczego mogę używać funkcji, zanim zostanie zdefiniowana w JavaScript?

168

Ten kod zawsze działa, nawet w różnych przeglądarkach:

function fooCheck() {
  alert(internalFoo()); // We are using internalFoo() here...

  return internalFoo(); // And here, even though it has not been defined...

  function internalFoo() { return true; } //...until here!
}

fooCheck();

Nie mogłem jednak znaleźć ani jednej wzmianki o tym, dlaczego to powinno działać. Po raz pierwszy zobaczyłem to w notatce prezentacyjnej Johna Resiga, ale zostało tylko wspomniane. Nie ma żadnego wyjaśnienia tej sprawy.

Czy ktoś mógłby mnie oświecić?

Edu Felipe
źródło
3
W nowszych wersjach przeglądarki Firefox to nie działa, jeśli kod jest w trybie try / catch. Zobacz te skrzypce: jsfiddle.net/qzzc1evt
Joshua Smith

Odpowiedzi:

217

functionDeklaracja jest magia i powoduje jego identyfikator na związanie zanim cokolwiek w swoim bloku kodu * jest wykonywany.

Różni się to od przypisania z functionwyrażeniem, które jest oceniane w normalnej kolejności odgórnej.

Jeśli zmieniłeś przykład, aby powiedzieć:

var internalFoo = function() { return true; };

przestanie działać.

Deklaracja funkcji jest syntaktycznie całkiem odrębna od wyrażenia funkcyjnego, mimo że wyglądają prawie identycznie i mogą być w niektórych przypadkach niejednoznaczne.

Jest to udokumentowane w standardzie ECMAScript , sekcja 10.1.3 . Niestety ECMA-262 nie jest bardzo czytelnym dokumentem, nawet według standardów!

*: zawierająca funkcję, blok, moduł lub skrypt.

bobince
źródło
Myślę, że to naprawdę nieczytelne. Właśnie przeczytałem sekcję 10.1.3, którą wskazałeś, i nie zrozumiałem, dlaczego zawarte tam przepisy powodują takie zachowanie. Dzięki za informację.
Edu Felipe
2
@bobince Okay, zacząłem wątpić w siebie, kiedy nie mogłem znaleźć ani jednej wzmianki o terminie „podnoszenie” na tej stronie. Mam nadzieję, że te komentarze mają wystarczającą ilość Google Juice ™, aby wszystko
naprawić
2
To popularne połączenie pytań i odpowiedzi. Rozważ aktualizację za pomocą łącza / fragmentu do specyfikacji z adnotacjami ES5. (Co jest nieco bardziej dostępne.)
1
Ten artykuł zawiera kilka przykładów: JavaScript-Scoping-and-Hoisting
Lombas
Odkryłem, że sporo bibliotek używa tej funkcji przed zdefiniowaniem, nawet niektóre języki oficjalnie na to pozwalają, np. Haskell. Szczerze mówiąc, może to nie być złe, ponieważ w niektórych przypadkach możesz pisać nieco bardziej wyraziście.
windmaomao
28

Nazywa się to PODNOSZENIE - wywołanie (wywołanie) funkcji przed jej zdefiniowaniem.

Dwa różne typy funkcji, o których chcę napisać to:

Funkcje wyrażeń i funkcje deklaracji

  1. Funkcje wyrażeń:

    Wyrażenia funkcyjne mogą być przechowywane w zmiennej, więc nie potrzebują nazw funkcji. Zostaną również nazwane jako funkcja anonimowa (funkcja bez nazwy).

    Aby wywołać (wywołać) te funkcje, zawsze potrzebują nazwy zmiennej . Ten rodzaj funkcji nie będzie działał, jeśli zostanie wywołany przed zdefiniowaniem, co oznacza, że ​​podnoszenie nie ma tutaj miejsca. Zawsze musimy najpierw zdefiniować funkcję wyrażenia, a następnie ją wywołać.

    let lastName = function (family) {
     console.log("My last name is " + family);
    };
    let x = lastName("Lopez");
    

    Oto jak możesz to zapisać w ECMAScript 6:

    lastName = (family) => console.log("My last name is " + family);
    
    x = lastName("Lopez");
    
  2. Funkcje deklaracji:

    Funkcje zadeklarowane za pomocą następującej składni nie są wykonywane natychmiast. Są „zapisywane do późniejszego wykorzystania” i będą wykonywane później, kiedy zostaną wywołane (przywołane). Ten typ funkcji działa, jeśli wywołasz ją PRZED lub PO, gdzie zostało zdefiniowane. Jeśli wywołasz funkcję deklaracji, zanim została zdefiniowana, funkcja podnoszenia działa poprawnie.

    function Name(name) {
      console.log("My cat's name is " + name);
    }
    Name("Chloe");
    

    Przykład podnoszenia:

    Name("Chloe");
    function Name(name) {
       console.log("My cat's name is " + name);
    }
    
Negin
źródło
let fun = theFunction; fun(); function theFunction() {}będzie również działać (węzeł i przeglądarki)
fider
14

Przeglądarka odczytuje kod HTML od początku do końca i może go wykonać, gdy jest odczytywany i parsowany na fragmenty wykonywalne (deklaracje zmiennych, definicje funkcji itp.) Ale w dowolnym momencie może używać tylko tego, co zostało zdefiniowane w skrypcie przed tym punktem.

Różni się to od innych kontekstów programowania, które przetwarzają (kompilują) cały kod źródłowy, być może łączą go z dowolnymi bibliotekami potrzebnymi do rozwiązywania odwołań i konstruują moduł wykonywalny, w którym rozpoczyna się wykonywanie.

Twój kod może odwoływać się do nazwanych obiektów (zmiennych, innych funkcji itp.), Które są zdefiniowane w dalszej części, ale nie możesz wykonać kodu odsyłającego, dopóki wszystkie elementy nie będą dostępne.

Kiedy zaznajomisz się z JavaScriptem, staniesz się dokładnie świadomy swojej potrzeby pisania rzeczy we właściwej kolejności.

Rewizja: Aby potwierdzić zaakceptowaną odpowiedź (powyżej), użyj Firebuga, aby przejść przez sekcję skryptów na stronie internetowej. Zobaczysz, jak przeskakuje od funkcji do funkcji, odwiedzając tylko pierwszą linię, zanim faktycznie wykona jakikolwiek kod.

dkretz
źródło
3

Niektóre języki wymagają zdefiniowania identyfikatorów przed użyciem. Powodem tego jest to, że kompilator używa pojedynczego przebiegu w kodzie źródłowym.

Ale jeśli jest wiele przepustek (lub niektóre czeki są odkładane), możesz doskonale żyć bez tego wymogu. W tym przypadku kod jest prawdopodobnie najpierw czytany (i interpretowany), a następnie ustawiane są linki.

Toon Krijthe
źródło
2

Użyłem JavaScript tylko trochę. Nie jestem pewien, czy to pomoże, ale wygląda bardzo podobnie do tego, o czym mówisz i może dać pewien wgląd:

http://www.dustindiaz.com/javascript-function-declaration-ambiguity/

RailsSon
źródło
Link nie jest już martwy.
mwclarke
1
Link nie działa.
Jerome Indefenzo,
Oto migawka z archive.org. Wygląda na to, że autor usunął całą witrynę z powodu przestarzałych treści, a nie, że ten wpis na blogu należy do tej kategorii.
jkmartindale
1

Ciało funkcji „internalFoo” musi gdzieś trafić w czasie analizy, więc kiedy kod jest odczytywany (czyli parsowany) przez interpreter JS, tworzona jest struktura danych funkcji i przypisywana jest jej nazwa.

Dopiero później, po uruchomieniu kodu, JavaScript faktycznie próbuje dowiedzieć się, czy „internalFoo” istnieje i co to jest i czy można go wywołać itp.

Aaron Digulla
źródło
-4

Z tego samego powodu foow globalnej przestrzeni nazw zawsze będą umieszczane :

if (test condition) {
    var foo;
}
Andrew Hedges
źródło
8
W rzeczywistości dzieje się tak z różnych powodów. ifBlok nie tworzy zakres, natomiast function()blok zawsze tworzy jeden. Prawdziwym powodem było to, że definicja globalnych nazw javascript odbywa się na etapie kompilacji, więc nawet jeśli kod nie działa, nazwa jest definiowana. (Przepraszam, że tak długo trwało komentowanie)
Edu Felipe