Jak działa konstrukcja (function () {}) () i dlaczego ludzie jej używają?

79

(function() {})()i jego kuzyn specyficzny dla jQuery (function($) {})(jQuery)pojawiają się cały czas w kodzie Javascript.

Jak działają te konstrukcje i jakie problemy rozwiązują?

Docenione przykłady

Tom Lehman
źródło
2
To nie jest dokładny duplikat. Odpowiedzi na inne pytania tak naprawdę nie mówią, dlaczego podaje jQuerysię argument.
Georg Schölly
1
Zawsze się zastanawiałem, dlaczego potrzebny jest dodatkowy nawias. Uważam, że ma to związek z niejednoznacznością między blokami a literałami obiektowymi z nawiasami klamrowymi. Zauważ, że nie są one wymagane, jeśli przypisujesz coś do czegoś, np. Var x = function () {} () działa dobrze.
jpsimons
1
Dlaczego to „zamknięte jako nie jest prawdziwe pytanie”? Pytanie jest jasne i istotne.
Chris

Odpowiedzi:

73

Wraz ze wzrostem popularności frameworków JavaScript, $znak ten był używany przy wielu różnych okazjach. Tak więc, aby złagodzić możliwe konflikty, możesz użyć tych konstrukcji:

(function ($){
  // Your code using $ here.
})(jQuery);

W szczególności jest to anonimowa deklaracja funkcji, która jest wykonywana natychmiastowo z przekazaniem głównego obiektu jQuery jako parametru. Wewnątrz tej funkcji możesz $odwoływać się do tego obiektu, nie martwiąc się, że inne struktury również znajdują się w zakresie.

Seb
źródło
4
Krótka i wymowna odpowiedź. +1!
pestaa
5
Technicznie jest to wyrażenie funkcyjne (! Deklaracja). IIFE
John Strickler
Czy możesz wyjaśnić, co masz na myśli, mówiąc: „... nie martwiąc się, że zakres obejmuje również inne ramy”?
Redtopia
@Redtopia. Zakres JavaScript jest definiowany na poziomie funkcji. Wszystko poza funkcją ma zasięg globalny. Pomyśl, ile Javascript jest zawartych na stronie internetowej. Każda zmienna poza zakresem funkcji grozi kolizją.
Alberto De Caro
Dziękuję za wyjaśnienie ... nie było dla mnie jasne, że odnosiłeś się do lokalnego zakresu funkcji anonimowej, którego nie można zobaczyć z zakresu globalnego, ani zakresu żadnej innej funkcji.
Redtopia
41

Jest to technika używana do ograniczania zakresu zmiennej; jest to jedyny sposób, aby zapobiec zanieczyszczaniu globalnej przestrzeni nazw przez zmienne.

var bar = 1; // bar is now part of the global namespace
alert(bar);

(function () {
   var foo = 1; // foo has function scope
   alert(foo); 
   // code to be executed goes here
})();
Daniel LeCheminant
źródło
7
... i nadal powinny być „globalne” w odniesieniu do twojego kodu.
tvanfosson
Więc odniesienie do Opery w przykładowym kodzie jest nieistotne / błędne?
Bobby Jack
1
@Bobby: w tym bloku mogą znajdować się dodatkowe zmienne / funkcje, które są używane tylko w Operze.
geowa4
19

1) Definiuje anonimową funkcję i wykonuje ją od razu.

2) Zwykle robi się to tak, aby nie zanieczyszczać globalnej przestrzeni nazw niechcianym kodem.

3) Musisz ujawnić niektóre metody z niego, wszystko zadeklarowane wewnątrz będzie „prywatne”, na przykład:

MyLib = (function(){
    // other private stuff here
    return {
        init: function(){
        }
    };

})();

Lub alternatywnie:

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

Chodzi o to, że można go używać na wiele sposobów, ale efekt pozostaje taki sam.

Evan Trimboli
źródło
1. Taka funkcja jest zdefiniowana w oddzielnym pliku, jak jest wykonywana? (wydaje się, że nikt nie wywołuje tej funkcji, więc w jaki sposób funkcja jest wyzwalana?) 2. Nie widziałem, aby funkcja na najwyższym poziomie zawierała instrukcję „return” ... Czytam dojo.util._doh ._browserRunner.js Dzięki!
Paul
1) Jak powiedziałem, wykonuje się sam, last () każe mu wykonać. 2) Nie musi koniecznie zwracać wartości, może również ustawiać wartości dla określonej właściwości. To był tylko przykład, zaktualizowałem odpowiedź o inną możliwość.
Evan Trimboli
9

To tylko anonimowa funkcja, która jest wywoływana natychmiast. Możesz najpierw utworzyć funkcję, a następnie ją wywołać i otrzymasz ten sam efekt:

(function(){ ... })();

pracuje jako:

temp = function(){ ... };
temp();

To samo możesz zrobić z nazwaną funkcją:

function temp() { ... }
temp();

Kod, który nazywasz specyficznym dla jQuery, jest taki, że używasz w nim obiektu jQuery. To po prostu anonimowa funkcja z parametrem, która jest wywoływana natychmiast.

Możesz zrobić to samo w dwóch krokach i możesz to zrobić z dowolnymi parametrami:

temp = function(answer){ ... };
temp(42);

Problem, który to rozwiązuje, polega na tym, że tworzy zamknięcie dla kodu w funkcji. Możesz zadeklarować w nim zmienne bez zanieczyszczania globalnej przestrzeni nazw, zmniejszając w ten sposób ryzyko konfliktów podczas używania jednego skryptu razem z drugim.

W konkretnym przypadku dla jQuery używasz go w trybie zgodności, w którym nie deklaruje nazwy $ jako aliasu dla jQuery. Wysyłając obiekt jQuery do domknięcia i nazywając parametr $, nadal można używać tej samej składni, co bez trybu zgodności.

Guffa
źródło
Jak skomentowałem odpowiedź @ spoulson, funkcja anonimowa to nie to samo, co zamknięcie.
Tim Down
7

Wyjaśnia tutaj, że twoja pierwsza konstrukcja zapewnia zakres dla zmiennych.

Zakres zmiennych jest określany na poziomie funkcji w języku JavaScript. Różni się to od tego, do czego możesz być przyzwyczajony w języku takim jak C # lub Java, w którym zmienne są ograniczone do bloku. Oznacza to, że jeśli zadeklarujesz zmienną w pętli lub w instrukcji if, będzie ona dostępna dla całej funkcji.

Jeśli kiedykolwiek będziesz musiał jawnie określić zakres zmiennej wewnątrz funkcji, możesz użyć do tego funkcji anonimowej. W rzeczywistości możesz utworzyć anonimową funkcję, a następnie wykonać ją od razu, a wszystkie zawarte w niej zmienne zostaną objęte zakresem funkcji anonimowej:

(function() {
  var myProperty = "hello world";
  alert(myProperty);
})();
alert(typeof(myProperty)); // undefined
Ewan Todd
źródło
6

Innym powodem, aby to zrobić, jest usunięcie wszelkich niejasności co do tego, którego $operatora frameworka używasz. Na przykład, aby wymusić działanie jQuery, możesz wykonać:

;(function($){
   ... your jQuery code here...
})(jQuery);

Przekazując $operator jako parametr i wywołując go w jQuery, $operator w funkcji jest zablokowany na jQuery, nawet jeśli masz załadowane inne platformy.

tvanfosson
źródło
To bardziej szczegółowa odmiana pierwotnego pytania, ale nadal jest to dobra informacja! :)
Bobby Jack
W ogóle nie jest to odpowiedź, ale poprawiłem ją ze względu na wspaniały pomysł, który miałeś!
Spidey
5

Innym zastosowaniem tej konstrukcji jest „przechwytywanie” wartości zmiennych lokalnych, które zostaną użyte w zamknięciu. Na przykład:

for (var i = 0; i < 3; i++) {
    $("#button"+i).click(function() {
        alert(i);
    });
}

Powyższy kod spowoduje wyświetlenie wszystkich trzech przycisków jako „3”. Z drugiej strony:

for (var i = 0; i < 3; i++) {
    (function(i) {
        $("#button"+i).click(function() {
            alert(i);
        });
    })(i);
}

Spowoduje to wyświetlenie trzech przycisków zgodnie z oczekiwaniami jako „0”, „1” i „2”.

Powodem tego jest to, że zamknięcie zachowuje odniesienie do otaczającej ramki stosu , która przechowuje bieżące wartości swoich zmiennych. Jeśli te zmienne zmienią się przed wykonaniem zamknięcia, wówczas zamknięcie zobaczy tylko najnowsze wartości, a nie wartości takie, jakie były w momencie utworzenia zamknięcia. Dzięki zawinięciu kreacji zamknięcia wewnątrz innej funkcji, jak w drugim przykładzie powyżej, bieżąca wartość zmiennej ijest zapisywana w ramce stosu funkcji anonimowej.

Greg Hewgill
źródło
Mały czubek, ale to nie ramka stosu jest zamknięta, to otaczające ją zakresy leksykalne. Dobre implementacje domknięć przechwytują tylko te symbole, które są faktycznie wspomniane wewnątrz zamknięcia, niektóre implementacje po prostu przechwytują wszystko (Ruby to robi, więc uważaj na to, co masz w pobliżu zamknięć). Jednym ze sposobów myślenia o obiekcie w OOP jest zamknięcie, w którym przechwycone symbole są jawnie zdefiniowane (atrybuty składowe obiektu).
KayEss
To prawda, ale tak naprawdę to tylko kwestia implementacji i koncepcyjnie można uznać, że ramka stosu jest tym, do czego odwołuje się zamknięcie. Wszystkie "obiekty" w stylu OOP w językach rodziny Lisp są wykonywane z domknięciami, jest to potężna technika.
Greg Hewgill
Tak, chociaż jeśli chcesz myśleć w kategoriach ramek stosu, zamknięciem jest łańcuch ramek stosu, jedna ramka dla każdej funkcji obejmującej w zakresie leksykalnym wraz z zakresem globalnym. Zakres leksykalny jest łatwiejszy do przemyślenia (zwłaszcza gdy wyjaśnia się komuś, kto jest nowy w domknięciach), ponieważ nie wdaje się w bezcelową (i mylącą) dyskusję na temat tego, która ramka stosu ma wiele poziomów zagnieżdżonych funkcji. Zakres leksykalny to po prostu to, co widzisz w kodzie źródłowym.
KayEss
4

Jest to uważane za zamknięcie . Oznacza to, że zawarty kod będzie działał we własnym zakresie leksykalnym. Oznacza to, że możesz zdefiniować nowe zmienne i funkcje, które nie będą kolidować z przestrzenią nazw używaną w kodzie poza zamknięciem.

var i = 0;
alert("The magic number is " + i);

(function() {
   var i = 99;
   alert("The magic number inside the closure is " + i);
})();

alert("The magic number is still " + i);

Spowoduje to wygenerowanie trzech wyskakujących okienek, pokazujących, że iw zamknięciu nie zmienia istniejącej wcześniej zmiennej o tej samej nazwie:

  • Magiczna liczba to 0
  • Magiczna liczba wewnątrz zamknięcia to 99
  • Magiczna liczba to nadal 0
spoulson
źródło
5
To nie jest zamknięcie, to po prostu pokazanie, że funkcja ma swój własny zakres. Zamknięcie jest tworzone, gdy funkcja jest zdefiniowana wewnątrz innej funkcji i staje się dostępna poza tą funkcją , zachowując w ten sposób dostęp do zmiennych, funkcji i parametrów w funkcji zewnętrznej nawet po zwróceniu tej funkcji zewnętrznej.
Tim Down
Po prostu zmień "(function () {..." na "var func = function () {...}; func ();". Tam, zamknięcie.
spoulson
Nie chcę nad tym pracować, ale ... nadal nie ma zamknięcia: poza funkcją zewnętrzną nie ma żadnej wewnętrznej funkcji. Prostym sposobem na utworzenie domknięcia w naszym przykładzie byłoby zwrócenie funkcji, która zaalarmowała i: var f = (function () {var i = 99; return function () {alert (i);};}) (); fa();
Tim Down
Punkt odnotowany. Sprowadza się do specyfiki realizacji.
spoulson
2

Są często używane we wtyczkach jQuery. Jak wyjaśniono w Przewodniku po tworzeniu wtyczek jQuery, wszystkie zmienne zadeklarowane wewnątrz { }są prywatne i nie są widoczne na zewnątrz, co pozwala na lepszą hermetyzację.

Darin Dimitrov
źródło
3
{}nie tworzą samodzielnie nowego zakresu w JavaScript. Zakres jest tworzony przez deklarację funkcji.
Annabelle
2

Jak powiedzieli inni, obaj definiują funkcje anonimowe, które są wywoływane natychmiast. Generalnie zawijam deklaracje klas JavaScript w tę strukturę, aby utworzyć statyczny zakres prywatny dla klasy. Mogę wtedy umieścić stałe dane, metody statyczne, programy obsługi zdarzeń lub cokolwiek innego w tym zakresie i będzie to widoczne tylko dla instancji klasy:

// Declare a namespace object.
window.MyLibrary = {};

// Wrap class declaration to create a private static scope.
(function() {
  var incrementingID = 0;

  function somePrivateStaticMethod() {
    // ...
  }

  // Declare the MyObject class under the MyLibrary namespace.
  MyLibrary.MyObject = function() {
    this.id = incrementingID++;
  };

  // ...MyObject's prototype declaration goes here, etc...
  MyLibrary.MyObject.prototype = {
    memberMethod: function() {
      // Do some stuff
      // Maybe call a static private method!
      somePrivateStaticMethod();
    }
  };
})();

W tym przykładzie MyObjectklasa jest przypisana do MyLibraryprzestrzeni nazw, więc jest dostępna. incrementingIDi somePrivateStaticMethod()nie są bezpośrednio dostępne poza zakresem funkcji anonimowej.

Annabelle
źródło
2

To jest po prostu przestrzeń nazw kodu JavaScript.

Na przykład możesz umieścić w nim dowolne zmienne lub funkcje, a z zewnątrz nie istnieją w tym zakresie. Więc kiedy hermetyzujesz tam wszystko, nie musisz się martwić o starcia.

Na ()końcu oznacza to samo przywołanie. Możesz tam również dodać argument, który stanie się argumentem Twojej funkcji anonimowej. Robię to często z jQuery i widać, dlaczego ...

(function($) {

    // Now I can use $, but it won't affect any other library like Prototype
})(jQuery);

Evan Trimboli zajmuje się resztą w swojej odpowiedzi .

Alex
źródło
1

To funkcja samo-wywołująca się. Coś w rodzaju skrótu do pisania

function DoSomeStuff($)
{
}

DoSomeStuff(jQuery);
Mateusz
źródło
1

Powyższy kod tworzy anonimową funkcję w linii 1, a następnie wywołuje ją w linii 3 z 0 argumentami. To skutecznie hermetyzuje wszystkie funkcje i zmienne zdefiniowane w tej bibliotece, ponieważ wszystkie funkcje będą dostępne tylko w tej anonimowej funkcji.

Jest to dobra praktyka, a jej powodem jest unikanie zanieczyszczania globalnej przestrzeni nazw zmiennymi i funkcjami, które mogłyby zostać zablokowane przez inne elementy JavaScript w całej witrynie.

Aby wyjaśnić, jak wywoływana jest funkcja, rozważ prosty przykład:

Jeśli masz dołączony ten pojedynczy wiersz JavaScript, zostanie on wywołany automatycznie bez jawnego wywoływania go:

alert('hello');

Więc weź ten pomysł i zastosuj go do tego przykładu:

(function() {
    alert('hello')
    //anything I define in here is scoped to this function only
}) (); //here, the anonymous function is invoked

Wynik końcowy jest podobny, ponieważ funkcja anonimowa jest wywoływana tak jak w poprzednim przykładzie.

wsanville
źródło
proszę zobaczyć moje pierwsze pytanie dotyczące komentarzy do Evana ... Dzięki!
Paul
//here, the anonymous function is invoked, proste, ale wcześniej tego nie widziałem :)
Stefan
1

Ponieważ dobre odpowiedzi na kod są już zajęte :) Dorzucę sugestię, aby obejrzeć kilka filmów wideo Johna Resiga wideo 1 , wideo 2 (wynalazca jQuery i master w JavaScript).

Kilka naprawdę dobrych spostrzeżeń i odpowiedzi zawartych w filmach.

Właśnie to robiłem w chwili, gdy zobaczyłem twoje pytanie.

John K.
źródło
0
function(){ // some code here }

to sposób na zdefiniowanie anonimowej funkcji w javascript. Mogą dać ci możliwość wykonania funkcji w kontekście innej funkcji (gdzie w przeciwnym razie możesz nie mieć takiej możliwości).

Justin Niessner
źródło