Jak działa ta składnia JavaScript / jQuery: (function (window, undefined) {}) (window)?

153

Czy kiedykolwiek zajrzałeś pod maskę kodu źródłowego jQuery 1.4 i zauważyłeś, jak jest on hermetyzowany w następujący sposób:

(function( window, undefined ) {

  //All the JQuery code here 
  ...

})(window);

Przeczytałem artykuł na temat przestrzeni nazw JavaScript i kolejny zatytułowany „ Ważna para parenów ”, więc wiem trochę o tym, co się tutaj dzieje.

Ale nigdy wcześniej nie widziałem tej konkretnej składni. Co to undefinedtam robi? I dlaczego windowtrzeba go przekazać, a potem znowu pojawić się na końcu?

dkinzer
źródło
3
Chciałem dodać, że Paul Irish mówi o tym w tym filmie: paulirish.com/2010/10-things-i-learned-from-the-jquery-source
Mottie
@Bergi oznaczyłeś moje pytanie jako duplikat innego, ale zadałem je prawie rok przed duplikatem. Obsada powinna być odwrotna.
dkinzer,
@dkinzer: Nie chodzi o to, żeby być pytanym wcześniej, chodzi o odpowiedź najwyższej jakości. Wprawdzie w tym przypadku to łeb w łeb, ale odpowiedź CMS jest najlepsza. Przyjdź na chat.stackoverflow.com/rooms/17, aby to omówić
Bergi

Odpowiedzi:

161

Niezdefiniowana jest normalną zmienną i można ją po prostu zmienić za pomocą undefined = "new value";. Zatem jQuery tworzy lokalną zmienną „niezdefiniowaną”, która jest NAPRAWDĘ niezdefiniowana.

Ze względu na wydajność zmienna okna jest lokalna. Ponieważ kiedy JavaScript wyszukuje zmienną, najpierw przechodzi przez zmienne lokalne, aż znajdzie nazwę zmiennej. Jeśli nie zostanie znaleziony, JavaScript przechodzi przez następny zakres itp., Aż przefiltruje zmienne globalne. Więc jeśli zmienna okna jest lokalna, JavaScript może ją szybciej wyszukać. Dalsze informacje: Przyspiesz swój JavaScript - Nicholas C. Zakas

Vincent
źródło
1
Dzięki! to był bardzo pouczający film. Myślę, że musisz zmodyfikować swoją odpowiedź, aby powiedzieć „zmienna okna jest lokalna”. Myślę, że to najlepsza odpowiedź. Policzyłem i stwierdziłem, że obiekt okna jest wywoływany co najmniej 17 razy w kodzie źródłowym JQuery. Tak więc przesunięcie okna do zakresu lokalnego musi mieć znaczący wpływ.
dkinzer
@DKinzer O tak, masz rację, oczywiście, że jest lokalny. Cieszę się, że mogłem ci pomóc =)
Vincent
1
Według tego zaktualizowanego testu jsperf.com/short-scope/5 przekazywanie „okna” jest w rzeczywistości wolniejsze, jeśli chodzi o uzyskanie stałej okna przez jQuery, a szczególnie złe dla Safari. Więc chociaż „undefined” ma przypadek użycia, nie jestem do końca przekonany, że „okno” jest szczególnie przydatne we wszystkich przypadkach.
hexalys
1
Ma również pozytywny wpływ na minimalizację kodu. Tak więc każde użycie „window” i „undefined” może zostać zastąpione krótszą nazwą przez minifyer.
TLindig
2
@ lukas.pukenis Kiedy tworzysz bibliotekę, która jest używana w milionach witryn internetowych, rozsądnie jest nie zakładać z góry, co zrobią szaleńcy.
JLRishe
54

Nieokreślony

Deklarując undefinedjako argument, ale nigdy nie przekazując mu wartości, zapewnia, że ​​jest ona zawsze niezdefiniowana, ponieważ jest to po prostu zmienna w zakresie globalnym, którą można nadpisać. Jest a === undefinedto bezpieczna alternatywa dla typeof a == 'undefined', która oszczędza kilka znaków. Dzięki temu kod jest bardziej przyjazny minifier, co undefinedmożna skrócić, una przykład, do zapisania kilku dodatkowych znaków.

Okno

Podanie windowjako argument powoduje zachowanie kopii w zakresie lokalnym, co wpływa na wydajność: http://jsperf.com/short-scope . Wszystkie dostępy windowbędą teraz musiały przejść o jeden poziom mniej w górę łańcucha lunety. Podobnie jak w undefinedprzypadku lokalnej kopii ponownie umożliwia bardziej agresywną minifikację.


Dygresja:

Chociaż może nie być to intencją twórców jQuery, przekazanie windowumożliwia łatwiejszą integrację biblioteki w środowiskach JavaScript po stronie serwera, na przykład node.js - gdzie nie ma windowobiektu globalnego . W takiej sytuacji wystarczy zmienić tylko jedną linię, aby zastąpić windowobiekt innym. W przypadku jQuery można stworzyć pozorowany windowobiekt i przekazać go w celu skrobania HTML ( może to zrobić biblioteka taka jak jsdom ).

David Tang
źródło
2
+1 dla wspomnieć minifikacji, życzę mogłem +2 do jsperf - Wersja minified jest (function(a,b){})(window);- ai bsą znacznie krótsze niż windowa undefined:)
Gnarf
+1 Słyszałem wcześniej o łańcuchu zasięgu , ale zapomniałem terminu. Dzięki!
alex
Jest to również alternatywa dla używania void (0), które było przeznaczone z tego samego powodu: void (0) to bezpieczny sposób na uzyskanie niezdefiniowanej wartości.
glmxndr
„Co powiesz” odnosi się do tego innego pytania: stackoverflow.com/questions/4945265/…
gnarf
@gnarf, dzięki za powiadomienie, że pytania zostały połączone. Zmienię odpowiedź, żeby była trochę bardziej sensowna;)
David Tang
16

Inni wyjaśnili undefined. undefinedjest jak zmienna globalna, którą można przedefiniować na dowolną wartość. Ta technika ma na celu zapobieżenie zerwaniu wszystkich niezdefiniowanych sprawdzeń, jeśli ktoś undefined = 10gdzieś napisał . Argument, który nigdy nie zostanie przekazany, ma gwarancję, że jest prawdziwy, undefinedniezależnie od wartości zmiennej undefined .

Powód przejścia przez okno można zilustrować na następującym przykładzie.

(function() {
   console.log(window);
   ...
   ...
   ...
   var window = 10;
})();

Co rejestruje konsola? Wartość windowprzedmiotu, prawda? Źle! 10? Źle! To rejestruje undefined. Interpreter JavaScript (lub kompilator JIT) przepisuje to w ten sposób -

(function() {
   var window; //and every other var in this function

   console.log(window);
   ...
   ...
   ...
   window = 10;

})();

Jeśli jednak otrzymasz windowzmienną jako argument, nie ma zmiennej, a zatem nie ma niespodzianek.

Nie wiem, czy jQuery to robi, ale jeśli redefiniujesz windowzmienną lokalną w dowolnym miejscu funkcji z jakiegokolwiek powodu, dobrym pomysłem jest pożyczenie jej z zakresu globalnego.

Chetan S
źródło
6

windowjest przekazywany w ten sposób na wypadek, gdyby ktoś zdecydował się na przedefiniowanie obiektu okna w IE, zakładam to samo undefined, na wypadek, gdyby później został ponownie przypisany.

Wierzchołek windowtego skryptu to po prostu nazwanie argumentu „okno”, argument, który jest bardziej lokalny niż windowodniesienie globalne i to, czego użyje kod wewnątrz tego zamknięcia. Na windowkońcu tak naprawdę określa się, co przekazać dla pierwszego argumentu, w tym przypadku obecne znaczenie window... mam nadzieję, że nie schrzaniłeś, windowzanim to się stanie.

Łatwiej o tym pomyśleć, pokazując najbardziej typowy przypadek używany w jQuery, .noConflict()obsługę wtyczek , więc dla większości kodu możesz nadal używać $, nawet jeśli oznacza to coś innego niż jQuerypoza tym zakresem:

(function($) {
  //inside here, $ == jQuery, it was passed as the first argument
})(jQuery);
Nick Craver
źródło
Dzięki. To ma sens. Chociaż myślę, że odpowiedź jest połączeniem tego i tego, co mówi @ C.Zakas.
dkinzer
@DKinzer Obawiam się, że nie jestem C. Zakas;)
Vincent
@Vincent, przepraszam za to :-)
dkinzer
5

Przetestowano na 1000000 iteracji. Taka lokalizacja nie miała wpływu na wydajność. Ani jednej milisekundy w 1000000 iteracjach. To jest po prostu bezużyteczne.

Semra
źródło
2
Nie wspominając o tym, że zamknięcie przenosi wszystkie zmienne wraz z wystąpieniem funkcji, więc jeśli masz 100 bajtów w zamknięciu i 1000 wystąpień, to jest to 100kb więcej pamięci.
Akash Kava,
@AkashKava i @Semra, nie sądzę, aby ten test naprawdę wyjaśniał, dlaczego przejdziesz przez okno w rzeczywistej sytuacji. Wzrost wydajności będzie widoczny tylko wtedy, gdy masz głęboko zagnieżdżone domknięcia, które mają dostęp do tej zmiennej dalej w łańcuchu zasięgu. oczywiście, jeśli twoja funkcja znajduje się tylko o jeden krok w łańcuchu zasięgu od zmiennej, nie zobaczysz dużego zysku. ale gdyby było tam na dole i musiało się dostać, windowmożesz zobaczyć jakiś zysk dzięki lokalnej kopii.
gabereal
@gabereal nowe silniki JavaScript rozwiązują problemy związane z zamknięciami w momencie kompilacji, nie ma wzrostu wydajności w przypadku zamykania w czasie wykonywania. Wątpię, czy jest jakiś wymierny wzrost wydajności, jeśli jesteś tak pewny siebie, możesz stworzyć przykład jsperf, aby zademonstrować wydajność. Jedyną wydajnością, jaką uzyskujemy, jest izolowanie zamknięć, co skutkuje lepszą minifikacją, która zmniejsza rozmiar i wydajność, uzyskuje się dzięki szybszemu pobieraniu i analizowaniu skryptu.
Akash Kava
@AkashKava co sprawia, że ​​myślisz, że jestem taki pewny siebie? Powiedziałem tylko „może coś zyskać” i „nie sądzę”. Mógłbym stworzyć test, ale wolałbym tego nie robić, ponieważ i tak nigdy nie używam tego wzoru. Czy możesz wyjaśnić, co masz na myśli, mówiąc, że „rozwiązuje problemy podczas samej kompilacji”? w przeciwieństwie do jakiej alternatywy? w jaki sposób silniki JavaScript działały wcześniej inaczej?
gabereal
Masz znaleźć kod, w którym okno jest zakreślone na końcu, ale nie zostało wprowadzone w parametrach funkcji, co to znaczy? zapewnia, że ​​parametry są zgodne z pierwotnym obiektem zasięgu globalnego, nawet jeśli nie ma żadnego parametru okna, który zakładam?
Webwoman