Dlaczego moje nazwy funkcji JavaScript są ze sobą sprzeczne?

97

Napisałem następujący skrypt, aby zobaczyć, co się dzieje, gdy zmienna i funkcja, do której jest przypisana funkcja, mają zderzenie nazw:

var f = function() {
    console.log("Me original.");
}

function f() {
    console.log("Me duplicate.");
}

f();

Wynik, który otrzymuję, to „Mój oryginał”. Dlaczego nie wywołano drugiej funkcji?

Ponadto, jeśli zmienię swoje pierwotne przypisanie na var f = new function() {, otrzymam „Mój oryginał”, a po nim stwierdzenie TypeError object is not a function. Czy ktoś może wyjaśnić?

ankush981
źródło
26
@ Dean.DePue - Nie ma zamieszania po stronie JavaScript. Zasady postępowania z nimi są dość jasne (i wyjaśnił je Benjamin w swojej odpowiedzi).
Quentin
4
Ciekawość, wciąż najlepszy sposób na poznanie języka. :-D
Cerbrus
2
Poza tym wyobrażam sobie, że coś tak niematerialnego jak „JavaScript” nie może „poczuć się” zdezorientowanym (lub jakąkolwiek emocją, jeśli o to chodzi) ;-)
Cerbrus,
2
Dlaczego podnoszenie powinno odwrócić kolejność w drugim przykładzie?
Cerbrus
5
Kroki do zdobywania wiedzy o javascript: 1) Używaj `` użyj ścisłego '' 2) Zawsze używaj jslint lub jshint 3) Sprawdź rzeczy, na które skarży się jslint lub jshint 4) Wypłucz i powtórz
steve-er-rino

Odpowiedzi:

170

Deklaracje funkcji są podnoszone (przenoszone na górę) w JavaScript. Chociaż niepoprawny pod względem kolejności analizowania, kod, który masz, jest semantycznie taki sam jak poniższy, ponieważ deklaracje funkcji są podnoszone:

function f() {
    console.log("Me duplicate.");
}
var f = function() {
    console.log("Me original.");
}


f();

Która z kolei, z wyjątkiem nazwy funkcji, jest taka sama jak:

var f = function() {
    console.log("Me duplicate.");
}
var f = function() {
    console.log("Me original.");
}


f();

Co z kolei ze względu na zmienne podnoszenie jest tym samym, co:

var f;
f = function() {
    console.log("Me duplicate.");
}
f = function() {
    console.log("Me original.");
}

f();

Co wyjaśnia, co otrzymujesz, nadpisujesz funkcję. Mówiąc bardziej ogólnie, varw JavaScript dozwolone są wielokrotne deklaracje - var x = 3; var x = 5jest to całkowicie legalne. W nowym standardzie ECMAScript 6 letzabraniają tego stwierdzenia.

Ten artykuł autorstwa @kangax wykonuje fantastyczną robotę w demistyfikacji funkcji w javascript

Benjamin Gruenbaum
źródło
2
Naprawdę można uprościć function f()do var f = function()tak dużo? Czy nazwy podnoszenia i funkcji to naprawdę jedyna różnica?
djechlin
6
@djechlin w kontekście tego pytania - tak. Generalnie jest to bardziej subtelne - zobacz stackoverflow.com/questions/336859/… . Z punktu widzenia kompilatora są one różne - ale z punktu widzenia programisty - jesteśmy na tyle blisko, aby to stwierdzić. Dlatego dodałem, że długie „choć niepoprawne pod względem kolejności analizowania, kod, który masz, jest semantycznie taki sam, jak„ zamiast mówić „jest taki sam jak”. Słuszna uwaga.
Benjamin Gruenbaum
1
@dotslash, proszę, nie edytuj swojego oryginalnego pytania i nie zmieniaj go, tutaj jest to uważane za złe maniery - również mieszanie kilku pytań w jedno jest tutaj również uważane za niewłaściwe. Możesz zamiast tego zadać nowe pytanie lub, jeśli uważasz, że jest zbyt drobne, poproś o wyjaśnienie w komentarzach (i tak one służą). W powyższym kodzie obie wersje fsą podnoszone, a "Me Original"wersja jest podnoszona później , każda z nich jest przenoszona na górę, ale w tej samej kolejności. Dodam tylko, że ogólnie nie należy nazywać kilku funkcji tak samo :)
Benjamin Gruenbaum
5
W trybie ścisłym nie możesz vardwukrotnie nadać tej samej nazwy w tym samym zakresie.
Hoffmann
4
"To powinno być oczywiste" - być może dla ciebie , ale w pewnym momencie nie było to dla mnie oczywiste i nie było oczywiste dla OP, kiedy o to zapytał, a nazewnictwo, a bardziej ogólnie, jak zarządza się środowiskiem leksykalnym w JavaScript, było jednym najtrudniejszych rzeczy do zrozumienia, kiedy po raz pierwszy uczyłem się JavaScript. Nie spieszyłbym się z obrażaniem ludzi, którzy tego nie rozumieją.
Benjamin Gruenbaum
10

Jeśli nie wygląda na to, że ktoś odpowiedział na Twoje dodatkowe pytanie, odpowiem na nie tutaj, chociaż generalnie powinieneś zadawać dodatkowe pytania jako osobne pytania.

Zapytałeś, dlaczego to:

var f = new function() {
    console.log("Me original.");
}

function f() {
    console.log("Me duplicate.");
}

f();

wydrukuje „Ja oryginał”. a następnie błąd.

To, co się tutaj dzieje, polega na tym, że newpowoduje, że funkcja jest używana jako konstruktor. Więc jest to równoważne z następującym:

function myConstructor() {
    console.log("Me original.");
}
var f = new myConstructor();

function f() {
    console.log("Me duplicate.");
}

f();

A dzięki funkcji podnoszenia, którą wyjaśnił Benjamin, powyższe jest zasadniczo równoważne z tym:

var myConstructor = function() {
    console.log("Me original.");
};
var f = function() {
    console.log("Me duplicate.");
};

f = new myConstructor();

f();

To wyrażenie:

var f = new function() {
    console.log("Me original.");
}

powoduje konstruowanie i przypisywanie nowego obiektu fprzy użyciu anonimowej funkcji jako konstruktora. „Ja oryginalny”. jest drukowany w trakcie wykonywania konstruktora. Ale skonstruowany obiekt sam w sobie nie jest funkcją, więc kiedy to w końcu się wykona:

f();

fpojawia się błąd, ponieważ nie jest funkcją.

JLRishe
źródło
Och cudownie! Wielkie dzięki za trud odpowiedzi! :) :)
ankush981
2

Wybacz mi, jeśli to niewłaściwy sposób dodawania punktu. Nie byłem tu zbyt często i przyjąłbym konstruktywne wskazówki i / lub krytykę.

Odpowiedź Benjamina doskonale odpowiada na pytanie OP, ale chciałbym dodać jedną poprawkę, która da nam pełną prezentację podnoszenia i jego osobliwości.

Jeśli zaczniemy oryginalny kod od wezwania do f:

f();

var f = function() {
   console.log("Me original.");
};

function f() {
   console.log("Me duplicate.");
}

f();

Wynik będzie wtedy:

Me duplicate.
Me original.

Powodem jest to, vara functionoświadczenia są podnoszone na nieco inne sposoby.

Na vartej deklaracji jest przesuwana do góry bieżącego Zakres *, ale każde zadanie nie jest podniesiona. Jeśli chodzi o wartość zadeklarowanej zmiennej, jest ona niezdefiniowana, dopóki nie zostanie osiągnięta pierwotna linia przypisania.

W przypadku functioninstrukcji podnoszona jest zarówno deklaracja, jak i definicja. Wyrażenia funkcyjne użyte w var f = function() {...konstrukcji nie są podnoszone.

Więc po podniesieniu wykonanie wygląda tak, jakby kod był:

var f; // declares var f, but does not assign it.

// name and define function f, shadowing the variable
function f() { 
  console.log("Me duplicate.");
}

// call the currently defined function f
f(); 

// assigns the result of a function expression to the var f,
// which shadows the hoisted function definition once past this point lexically
f = function() { 
  console.log("Me original."); 
}

// calls the function referenced by the var f
f();

* Cały zakres JavaScript jest leksykalny lub funkcjonalny, ale wydawało się, że użycie słowa f w tym momencie byłoby po prostu pomieszane.

codelahoma
źródło