Kolejność funkcji JavaScript: dlaczego ma to znaczenie?

106

Oryginalne pytanie:

JSHint narzeka, gdy mój JavaScript wywołuje funkcję, która jest zdefiniowana w dalszej części strony niż wywołanie jej. Jednak moja strona dotyczy gry i żadne funkcje nie są wywoływane, dopóki całość nie zostanie pobrana. Dlaczego więc funkcje kolejności pojawiają się w moim kodzie?

EDYCJA: Myślę, że mogłem znaleźć odpowiedź.

http://www.adequatelygood.com/2010/2/JavaScript-Scoping-and-Hoisting

Jęczę w środku. Wygląda na to, że muszę spędzić KOLEJNY dzień na zamawianiu sześciu tysięcy wierszy kodu. Krzywa uczenia się z javascript nie jest wcale stroma, ale jest bardzo dłuuuuga.

Chris Tolworthy
źródło
+1 za doskonałe odniesienie w aktualizacji. Mam nadzieję, że przekonuje Cię to, że tak naprawdę nie musisz ponownie zamawiać kodu. :)
awm

Odpowiedzi:

294

tl; dr Jeśli nie dzwonisz, dopóki wszystko się nie załaduje, powinno być dobrze.


Edycja: przegląd obejmujący również niektóre deklaracje ES6 ( let, const): https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Scope_Cheatsheet

To dziwne zachowanie zależy od

  1. Jak definiujesz funkcje i
  2. Kiedy do nich dzwonisz.

Oto kilka przykładów.

bar(); //This won't throw an error
function bar() {}

foo(); //This will throw an error
var foo = function() {}
bar();
function bar() {
    foo(); //This will throw an error
}
var foo = function() {}
bar();
function bar() {
    foo(); //This _won't_ throw an error
}
function foo() {}
function bar() {
    foo(); //no error
}
var foo = function() {}
bar();

To z powodu czegoś, co nazywa się podnoszeniem !

Istnieją dwa sposoby definiowania funkcji: deklaracja funkcji i wyrażenie funkcji . Różnica jest irytująca i niewielka, więc powiedzmy trochę zła rzecz: jeśli piszesz to w ten sposób function name() {}, jest to deklaracja , a kiedy piszesz to jako var name = function() {}(lub anonimowa funkcja przypisana do zwrotu, takie rzeczy), to wyrażenie funkcyjne .

Najpierw przyjrzyjmy się, jak obsługiwane są zmienne:

var foo = 42;

//the interpreter turns it into this:
var foo;
foo = 42;

Teraz, jak obsługiwane są deklaracje funkcji :

var foo = 42;
function bar() {}

//turns into
var foo; //Insanity! It's now at the top
function bar() {}
foo = 42;

Te varstwierdzenia „rzuca” na tworzenie się foona samym szczycie, ale jeszcze nie przypisać wartość do niego. Deklaracja funkcji jest następna w linii, a na końcu przypisywana jest wartość foo.

A co z tym?

bar();
var foo = 42;
function bar() {}
//=>
var foo;
function bar() {}
bar();
foo = 42;

Tylko deklaracja od fooprzesuwa się do góry. Przypisanie następuje dopiero po wezwaniu bar, gdzie było przed całym podniesieniem.

I wreszcie, dla zwięzłości:

bar();
function bar() {}
//turns to
function bar() {}
bar();

A co z wyrażeniami funkcyjnymi ?

var foo = function() {}
foo();
//=>
var foo;
foo = function() {}
foo();

Podobnie jak zwykłe zmienne, najpierw foozostanie ogłoszony w najwyższym punkcie zakresu, to jest przypisana wartość.

Zobaczmy, dlaczego drugi przykład generuje błąd.

bar();
function bar() {
    foo();
}
var foo = function() {}
//=>
var foo;
function bar() {
    foo();
}
bar();
foo = function() {}

Jak widzieliśmy wcześniej, tylko tworzenie foojest podnoszone, przypisanie pojawia się tam, gdzie pojawiło się w „oryginalnym” (niewniesionym) kodzie. Kiedy barjest wywoływana, to jest wcześniej fooprzypisywana wartość, więc foo === undefined. Teraz w treści funkcji barjest to tak, jakbyś robił undefined(), co powoduje błąd.

Zirak
źródło
Przepraszam, że to wykopuję, ale czy przeciążenia takie jak Array.prototype.someMethod = function () {} zostały podniesione? Wydaje się, że pojawiają się błędy, jeśli tego typu rzeczy znajdują się na końcu mojego skryptu.
Edge
Jak upewniasz się, że wszystko się ładuje ? Czy jest jakaś powszechna praktyka?
zwcloud
6

Głównym powodem jest prawdopodobnie to, że JSLint wykonuje tylko jedno przejście do pliku, więc nie wie, że zdefiniujesz taką funkcję.

Jeśli użyłeś składni instrukcji funkcji

function foo(){ ... }

Właściwie nie ma żadnej różnicy w miejscu deklarowania funkcji (zawsze zachowuje się tak, jakby deklaracja była na początku).

Z drugiej strony, jeśli twoja funkcja została ustawiona jak zwykła zmienna

var foo = function() { ... };

Musisz zagwarantować, że nie zadzwonisz przed inicjalizacją (w rzeczywistości może to być źródłem błędów).


Ponieważ zmiana kolejności ton kodu jest skomplikowana i może sama w sobie być źródłem błędów, sugerowałbym, abyś poszukał obejścia. Jestem prawie pewien, że możesz wcześniej podać JSLint nazwę zmiennych globalnych, więc nie narzeka na niezadeklarowane rzeczy.

Umieść komentarz na początku pliku

/*globals foo1 foo2 foo3*/

Możesz też użyć do tego pola tekstowego. (Myślę też, że możesz przekazać to w argumentach do wewnętrznej funkcji jslint, jeśli możesz się w to wtrącać.)

hugomg
źródło
Dzięki. Więc linia / * globals * / będzie działać? Dobrze - wszystko, aby JsHint mnie polubił. Wciąż jestem nowy w JavaScript i dostaję niewytłumaczalne przerwy, kiedy odświeżam stronę, ale nie zgłoszono żadnych błędów. Doszedłem więc do wniosku, że rozwiązaniem jest gra zgodnie ze wszystkimi zasadami, a następnie sprawdzenie, czy to się dalej dzieje.
Chris Tolworthy,
4

Zbyt wielu ludzi naciska na arbitralne zasady dotyczące pisania JavaScript. Większość zasad to bzdury.

Podnoszenie funkcji jest funkcją JavaScript, ponieważ jest dobrym pomysłem.

Kiedy masz funkcję wewnętrzną, która często jest użytecznością funkcji wewnętrznych, dodanie jej na początku funkcji zewnętrznej jest akceptowalnym stylem pisania kodu, ale ma tę wadę, że musisz przeczytać szczegóły, aby dostać się do czego zewnętrzna funkcja tak.

Powinieneś trzymać się jednej zasady w całym swoim kodzie, albo umieszczać prywatne funkcje jako pierwsze albo ostatnie w swoim module lub funkcji. JSHint jest dobry do wymuszania spójności, ale powinieneś ABSOLUTNIE dostosować .jshintrc do swoich potrzeb, NIE dostosowywać kodu źródłowego do zwariowanych koncepcji kodowania innych ludzi.

Jeden styl kodowania, którego możesz zobaczyć na wolności, którego powinieneś unikać, ponieważ nie daje żadnych korzyści, a jedynie możliwy ból związany z refaktoryzacją:

function bigProcess() {
    var step1,step2;
    step1();
    step2();

    step1 = function() {...};
    step2 = function() {...};
}

Właśnie tego należy unikać podczas podnoszenia. Po prostu naucz się języka i wykorzystaj jego mocne strony.

Henrik Vendelbo
źródło
2

Podnoszone są tylko deklaracje funkcji, a nie wyrażenie funkcji (przypisanie).

Abhay Srivastav
źródło