Jaki jest cel pakowania całych plików JavaScript w anonimowe funkcje, takie jak „(function () {…}) ()”?

584

Ostatnio czytałem dużo skryptów Javascript i zauważyłem, że cały plik jest zawijany w następujący sposób w plikach .js do zaimportowania.

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

Jaki jest tego powód, a nie prosty zestaw funkcji konstruktora?

Andrew Kou
źródło
6
Ponieważ wyobrażam sobie, że z tego skorzysta wiele osób, proszę nie zapominać o zamknięciu;
dgh
5
Ta technika nazywa się „IIFE”. Wyrażenie to oznacza natychmiastowe wywołanie funkcji wyrażenie en.wikipedia.org/wiki/Immedently-invoked_function_expression
Adrien Be

Odpowiedzi:

786

Zwykle dotyczy to przestrzeni nazw (patrz później) i kontrolowania widoczności funkcji składowych i / lub zmiennych. Pomyśl o tym jak o definicji obiektu. Techniczna nazwa tego wyrażenia to Natychmiastowe wywołanie funkcji (IIFE). Wtyczki jQuery są zwykle pisane w ten sposób.

W Javascript możesz zagnieżdżać funkcje. Tak więc następujące są legalne:

function outerFunction() {
   function innerFunction() {
      // code
   }
}

Teraz możesz dzwonić outerFunction(), ale widoczność innerFunction()jest ograniczona do zakresu outerFunction(), co oznacza, że ​​jest prywatna outerFunction(). Zasadniczo przestrzega tej samej zasady co zmienne w JavaScript:

var globalVariable;

function someFunction() {
   var localVariable;
}

Odpowiednio:

function globalFunction() {

   var localFunction1 = function() {
       //I'm anonymous! But localFunction1 is a reference to me!
   };

   function localFunction2() {
      //I'm named!
   }
}

W powyższym scenariuszu możesz dzwonić globalFunction()z dowolnego miejsca, ale nie możesz dzwonić localFunction1lub localFunction2.

Pisząc (function() { ... })(), robisz, że kod w pierwszym zestawie nawiasów jest dosłowny dla funkcji (co oznacza, że ​​cały „obiekt” jest w rzeczywistości funkcją). Następnie wywołujesz funkcję (ostatnią ()), którą właśnie zdefiniowałeś. Główną zaletą tego, o czym wspomniałem wcześniej, jest to, że możesz mieć prywatne metody / funkcje i właściwości:

(function() {
   var private_var;

   function private_function() {
     //code
   }
})();

W pierwszym przykładzie jawnie wywołujesz globalFunctionwedług nazwy, aby go uruchomić. To znaczy, po prostu byś to zrobił globalFunction(). Ale w powyższym przykładzie nie tylko definiujesz funkcję; definiujesz i wywołujesz to za jednym razem. Oznacza to, że po załadowaniu plik JavaScript jest natychmiast wykonywany. Oczywiście możesz zrobić:

function globalFunction() {
    // code
}
globalFunction();

Zachowanie byłoby zasadniczo takie samo, z wyjątkiem jednej istotnej różnicy: unikasz zanieczyszczania zasięgu globalnego, gdy używasz IIFE (w konsekwencji oznacza to również, że nie możesz wywołać funkcji wiele razy, ponieważ nie ma ona nazwy, ale ponieważ ta funkcja ma być wykonana tylko wtedy, gdy tak naprawdę nie stanowi problemu).

Z IIFE fajną rzeczą jest to, że możesz także definiować rzeczy wewnątrz i ujawniać tylko te części, które chcesz, na zewnątrz, więc (przykład przestrzeni nazw, dzięki której możesz w zasadzie stworzyć własną bibliotekę / wtyczkę):

var myPlugin = (function() {
 var private_var;

 function private_function() {
 }

 return {
    public_function1: function() {
    },
    public_function2: function() {
    }
 }
})()

Teraz możesz dzwonić myPlugin.public_function1(), ale nie masz dostępu private_function()! Bardzo podobny do definicji klasy. Aby to lepiej zrozumieć, polecam następujące linki do dalszej lektury:

EDYTOWAĆ

Zapomniałem wspomnieć. W tym finale ()możesz przekazać wszystko, co chcesz w środku. Na przykład, kiedy tworzysz wtyczki jQuery, przekazujesz jQuerylub $lubisz:

(function(jQ) { ... code ... })(jQuery) 

To, co tutaj robisz, polega na zdefiniowaniu funkcji, która przyjmuje jeden parametr (nazywany jQzmienną lokalną i znaną tylko tej funkcji). Następnie wywołujesz funkcję i przekazujesz parametr (nazywany także jQuery, ale ten pochodzi ze świata zewnętrznego i jest odniesieniem do samego jQuery). Nie ma takiej potrzeby, ale istnieją pewne zalety:

  • Możesz ponownie zdefiniować parametr globalny i nadać mu nazwę, która ma sens w zakresie lokalnym.
  • Istnieje niewielka przewaga wydajności, ponieważ szybsze jest wyszukiwanie rzeczy w zakresie lokalnym zamiast konieczności przejścia łańcucha zasięgu do zakresu globalnego.
  • Istnieją zalety kompresji (minimalizacji).

Wcześniej opisałem, jak te funkcje działają automatycznie podczas uruchamiania, ale jeśli działają one automatycznie, kto przekazuje argumenty? Ta technika zakłada, że ​​wszystkie potrzebne parametry są już zdefiniowane jako zmienne globalne. Więc jeśli jQuery nie został jeszcze zdefiniowany jako zmienna globalna, ten przykład nie działałby. Jak można się domyślić, jedną z rzeczy, które jquery.js robi podczas inicjalizacji, jest zdefiniowanie globalnej zmiennej „jQuery”, a także jej bardziej znanej zmiennej globalnej „$”, która pozwala na działanie tego kodu po uwzględnieniu jQuery.

Vivin Paliath
źródło
14
Bardzo fajnie, dobrze rozumiem przestrzeń nazw, ale widziałem wiele z twojego ostatniego przykładu i nie mogłem zrozumieć, co ludzie próbują osiągnąć. To naprawia wszystko.
Andrew Kou,
34
Świetny post. Wielkie dzięki.
Darren
4
Myślę, że dodanie wiodącego i końcowego średnika ';' dopełniłby przykład - w ;(function(jQ) { ... code ... })(jQuery);ten sposób, jeśli ktoś pominął średnik w swoim skrypcie, nie złamałby twojego, szczególnie jeśli planujesz zminimalizować i połączyć swój skrypt z innym.
Taras Alenin
3
fajny post, podoba mi się nacisk na zmienne prywatne. Podoba mi się również otwarcie na module / zamknięciach modułu (public_function1 & public_function2) i sposób, w jaki przekazujesz zmienne, chociaż nieznaczne wyjście poza zakres jest miłym wprowadzeniem. Dodałem również odpowiedź, która koncentruje się na tym, co, jak sądzę, jest źródłem składni i różnicami między instrukcją funkcji a wyrażeniem funkcji i tym, co uważam za „tylko konwencję” vs „jedynym sposobem na osiągnięcie tego wyniku”.
Adrien Be
4
Świetny post, myślę, że może bardziej na temat tego, w jaki sposób przekazywanie zmiennych do funkcji wykonującej się samo jest korzystne. Kontekst funkcji samowykonywania jest czysty - brak danych. Możesz przekazać w kontekście, robiąc to, (function (context) { ..... })(this)co następnie pozwala ci dołączyć wszystko, co chcesz, do kontekstu nadrzędnego, odsłaniając go.
Callum Linington,
79

W skrócie

Podsumowanie

W najprostszej formie technika ta ma na celu zawinięcie kodu w zakres funkcji .

Pomaga zmniejszyć szanse na:

  • kolidowanie z innymi aplikacjami / bibliotekami
  • zanieczyszczający lepszy (najprawdopodobniej globalny) zakres

To nie wykryć, gdy dokument jest gotowy - to nie jest jakaś document.onloadaniwindow.onload

Jest powszechnie znany jako Immediately Invoked Function Expression (IIFE)lub Self Executing Anonymous Function.

Kod wyjaśniony

var someFunction = function(){ console.log('wagwan!'); };

(function() {                   /* function scope starts here */
  console.log('start of IIFE');

  var myNumber = 4;             /* number variable declaration */
  var myFunction = function(){  /* function variable declaration */
    console.log('formidable!'); 
  };
  var myObject = {              /* object variable declaration */
    anotherNumber : 1001, 
    anotherFunc : function(){ console.log('formidable!'); }
  };
  console.log('end of IIFE');
})();                           /* function scope ends */

someFunction();            // reachable, hence works: see in the console
myFunction();              // unreachable, will throw an error, see in the console
myObject.anotherFunc();    // unreachable, will throw an error, see in the console

W powyższym przykładzie każda zmienna zdefiniowana w funkcji (tj. Zadeklarowana za pomocą var) będzie „prywatna” i dostępna TYLKO w zakresie funkcji (jak to ujęła Vivin Paliath). Innymi słowy, te zmienne nie są widoczne / osiągalne poza funkcją. Zobacz demo na żywo .

JavaScript ma zasięg funkcji. „Parametry i zmienne zdefiniowane w funkcji nie są widoczne poza funkcją, a zmienna zdefiniowana w dowolnym miejscu funkcji jest widoczna wszędzie w funkcji”. (z „Javascript: The Good Parts”).


Więcej szczegółów

Kod alternatywny

Ostatecznie kod opublikowany wcześniej można również wykonać w następujący sposób:

var someFunction = function(){ console.log('wagwan!'); };

var myMainFunction = function() {
  console.log('start of IIFE');

  var myNumber = 4;
  var myFunction = function(){ console.log('formidable!'); };
  var myObject = { 
    anotherNumber : 1001, 
    anotherFunc : function(){ console.log('formidable!'); }
  };
  console.log('end of IIFE');
};

myMainFunction();          // I CALL "myMainFunction" FUNCTION HERE
someFunction();            // reachable, hence works: see in the console
myFunction();              // unreachable, will throw an error, see in the console
myObject.anotherFunc();    // unreachable, will throw an error, see in the console

Zobacz demo na żywo .


Korzenie

Iteracja 1

Pewnego dnia ktoś prawdopodobnie pomyślał „musi istnieć sposób na uniknięcie nazwania„ myMainFunction ”, ponieważ wszystko, czego chcemy, to natychmiastowe wykonanie”.

Jeśli wrócisz do podstaw, przekonasz się, że:

  • expression: coś wartościującego. to znaczy3+11/x
  • statement: Linia (y) kodu coś robią, ale to nie nie oceniają się do wartości. to znaczyif(){}

Podobnie wyrażenia funkcyjne są wartościowane. A jedną konsekwencją (zakładam?) Jest to, że można je natychmiast wywołać:

 var italianSayinSomething = function(){ console.log('mamamia!'); }();

Nasz bardziej złożony przykład to:

var someFunction = function(){ console.log('wagwan!'); };

var myMainFunction = function() {
  console.log('start of IIFE');

  var myNumber = 4;
  var myFunction = function(){ console.log('formidable!'); };
  var myObject = { 
    anotherNumber : 1001, 
    anotherFunc : function(){ console.log('formidable!'); }
  };
  console.log('end of IIFE');
}();

someFunction();            // reachable, hence works: see in the console
myFunction();              // unreachable, will throw an error, see in the console
myObject.anotherFunc();    // unreachable, will throw an error, see in the console

Zobacz demo na żywo .

Iteracja 2

Następnym krokiem jest myśl „ var myMainFunction =po co, skoro nawet go nie używamy !?”.

Odpowiedź jest prosta: spróbuj to usunąć, na przykład poniżej:

 function(){ console.log('mamamia!'); }();

Zobacz demo na żywo .

Nie zadziała, ponieważ „deklaracje funkcji nie są wywoływalne” .

Sztuką jest to, że poprzez usunięcie var myMainFunction =my przekształcił wyrażenie funkcyjne w deklaracji funkcji . Więcej informacji na ten temat można znaleźć w linkach w „Zasobach”.

Następne pytanie brzmi: „dlaczego nie mogę zachować tego jako wyrażenia funkcji z czymś innym niż var myMainFunction =?

Odpowiedź brzmi „możesz”, a tak naprawdę można to zrobić na wiele sposobów: dodając a +, a !, a -lub owijanie w parę nawiasów (tak jak to obecnie odbywa się w konwencji) i więcej wierzę. Jako przykład:

 (function(){ console.log('mamamia!'); })(); // live demo: jsbin.com/zokuwodoco/1/edit?js,console.

lub

 +function(){ console.log('mamamia!'); }(); // live demo: jsbin.com/wuwipiyazi/1/edit?js,console

lub

 -function(){ console.log('mamamia!'); }(); // live demo: jsbin.com/wejupaheva/1/edit?js,console

Po dodaniu odpowiedniej modyfikacji do tego, co było kiedyś naszym „Kodem alternatywnym”, wracamy do dokładnie tego samego kodu, który został użyty w przykładzie „Kod wyjaśniony”

var someFunction = function(){ console.log('wagwan!'); };

(function() {
  console.log('start of IIFE');

  var myNumber = 4;
  var myFunction = function(){ console.log('formidable!'); };
  var myObject = { 
    anotherNumber : 1001, 
    anotherFunc : function(){ console.log('formidable!'); }
  };
  console.log('end of IIFE');
})();

someFunction();            // reachable, hence works: see in the console
myFunction();              // unreachable, will throw an error, see in the console
myObject.anotherFunc();    // unreachable, will throw an error, see in the console

Przeczytaj więcej o Expressions vs Statements:


Demystifying Scopes

Można się zastanawiać: „co się stanie, gdy NIE zdefiniujesz zmiennej„ poprawnie ”w funkcji - tzn. Wykonasz proste przypisanie?

(function() {
  var myNumber = 4;             /* number variable declaration */
  var myFunction = function(){  /* function variable declaration */
    console.log('formidable!'); 
  };
  var myObject = {              /* object variable declaration */
    anotherNumber : 1001, 
    anotherFunc : function(){ console.log('formidable!'); }
  };
  myOtherFunction = function(){  /* oops, an assignment instead of a declaration */
    console.log('haha. got ya!');
  };
})();
myOtherFunction();         // reachable, hence works: see in the console
window.myOtherFunction();  // works in the browser, myOtherFunction is then in the global scope
myFunction();              // unreachable, will throw an error, see in the console

Zobacz demo na żywo .

Zasadniczo, jeśli zmienna, która nie została zadeklarowana w swoim bieżącym zakresie, ma przypisaną wartość, wówczas „wyszukiwanie łańcucha zasięgu następuje, dopóki nie znajdzie zmiennej lub nie dotrze do zasięgu globalnego (w którym momencie ją utworzy)”.

W środowisku przeglądarki (w porównaniu ze środowiskiem serwera takim jak nodejs) zasięg globalny jest definiowany przez windowobiekt. Dlatego możemy to zrobić window.myOtherFunction().

Moja wskazówka „dobrych praktyk” na ten temat polega na tym, aby zawsze używać varpodczas definiowania czegokolwiek : niezależnie od tego, czy jest to liczba, obiekt czy funkcja, a nawet w zakresie globalnym. To znacznie upraszcza kod.

Uwaga:

  • javascript nie ma block scope(Aktualizacja: zmienne lokalne zakresu bloków dodane w ES6 ).
  • javascript ma tylko function scope& global scope( windowzakres w środowisku przeglądarki)

Przeczytaj więcej o Javascript Scopes:


Zasoby


Następne kroki

Po otrzymaniu tej IIFEkoncepcji prowadzi ona do tego module pattern, co zwykle wykonuje się przez wykorzystanie tego wzoru IIFE. Baw się dobrze :)

Adrien Be
źródło
Bardzo pomocny. Wielkie dzięki!
Christoffer Helgelin Hald
Fajnie, wolę wersję demo :)
Fabrizio Bertoglio
Takie świetne wytłumaczenie. Dziękuję Ci!
Vikram Khemlani,
26

JavaScript w przeglądarce ma naprawdę tylko kilka skutecznych zakresów: zakres funkcji i zasięg globalny.

Jeśli zmienna nie należy do zakresu funkcji, ma zasięg globalny. Zmienne globalne są na ogół złe, więc jest to konstrukcja pozwalająca zachować zmienne biblioteki dla siebie.

Gareth
źródło
1
Ale czy sama funkcja konstruktora nie zapewnia zakresu dla własnych zmiennych?
Andrew Kou,
1
Tak, każda funkcja zdefiniowana w tej bibliotece może definiować własne zmienne lokalne, ale pozwala to na dzielenie zmiennych między funkcjami bez ich wycieku poza bibliotekę
Gareth
@Gareth, więc pozwala to na „globalne” zmienne w zakresie (;
Francisco Presencia
2
@FranciscoPresencia „globalny w zakresie” nie jest przydatnym zwrotem, ponieważ w zasadzie to właśnie oznacza „zakres”. Cały sens „globalnego” zakresu polega na tym, że jest to zakres, do którego mają dostęp wszystkie inne zakresy.
Gareth,
19

To się nazywa zamknięcie. Zasadniczo uszczelnia kod wewnątrz funkcji, aby inne biblioteki nie zakłócały go. Jest to podobne do tworzenia przestrzeni nazw w skompilowanych językach.

Przykład. Załóżmy, że piszę:

(function() {

    var x = 2;

    // do stuff with x

})();

Teraz inne biblioteki nie mogą uzyskać dostępu do zmiennej, xktórą utworzyłem w mojej bibliotece.

Joel
źródło
7
Ostrożnie z terminologią. Przestrzeń nazw oznacza, że ​​do zmiennych można uzyskać dostęp z zewnątrz poprzez zaadresowanie przestrzeni nazw (zazwyczaj za pomocą prefiksu). Chociaż jest to możliwe w Javascript, nie jest to pokazane tutaj
Gareth
Zgadzam się, że nie jest dokładnie jak nazw, jednak można zapewnić podobną funkcjonalność wracając obiektu z właściwości, które chcesz opublikować: (function(){ ... return { publicProp1: 'blah' }; })();. Oczywiście nie idealnie równoległe do przestrzeni nazw, ale może pomóc w takim myśleniu.
Joel
w twoim przykładzie x jest nadal zmienną prywatną ... Pomimo tego, że otuliłeś ją IIFE. śmiało i spróbuj uzyskać dostęp do x poza funkcją, nie możesz ..
RayLoveless
Twój punkt jest nieważny. Nawet w poniższej funkcji inne biblioteki nie mogą uzyskać dostępu do x. function () {var x = 2}
RayLoveless,
@RayLoveless Zgadzam się. Nie przeczę temu twierdzeniu. W rzeczywistości uczyniłem to samo, co ostatnie zdanie tej odpowiedzi.
Joel
8

Możesz używać zamknięć funkcji jako danych również w większych wyrażeniach, jak również w tej metodzie określania obsługi przeglądarki dla niektórych obiektów HTML5.

   navigator.html5={
     canvas: (function(){
      var dc= document.createElement('canvas');
      if(!dc.getContext) return 0;
      var c= dc.getContext('2d');
      return typeof c.fillText== 'function'? 2: 1;
     })(),
     localStorage: (function(){
      return !!window.localStorage;
     })(),
     webworkers: (function(){
      return !!window.Worker;
     })(),
     offline: (function(){
      return !!window.applicationCache;
     })()
    }
Kennebec
źródło
Co robi !! zrobić?
1,21 gigawatów
!! konwertuje wartość na jego reprezentację logiczną (prawda / fałsz).
Liam,
7

Oprócz utrzymywania zmiennych lokalnych, jednym z bardzo przydatnych zastosowań jest pisanie biblioteki przy użyciu zmiennej globalnej, można nadać jej krótszą nazwę, aby można było z niej korzystać w bibliotece. Jest często używany do pisania wtyczek jQuery, ponieważ jQuery pozwala wyłączyć zmienną $ wskazującą na jQuery, używając jQuery.noConflict (). W przypadku wyłączenia kod nadal może używać $ i nie pękać, jeśli po prostu:

(function($) { ...code...})(jQuery);
Coronus
źródło
3
  1. Aby uniknąć kolizji z innymi metodami / bibliotekami w tym samym oknie,
  2. Unikaj zasięgu globalnego, ustaw zasięg lokalny,
  3. Aby przyspieszyć debugowanie (zasięg lokalny),
  4. JavaScript ma tylko zakres funkcji, więc pomoże również w kompilacji kodów.
Vivek Mehta
źródło
1

Powinniśmy również użyć „use strict” w funkcji scope, aby upewnić się, że kod powinien być wykonywany w „trybie ścisłym”. Przykładowy kod pokazano poniżej

(function() {
    'use strict';

    //Your code from here
})();
Neha Jain
źródło
Dlaczego powinniśmy stosować ścisłe?
nbro
Sprawdź ten artykuł: stackoverflow.com/questions/1335851/…
Neha Jain
Naprawdę nie odpowiada na pytanie!
Pritam Banerjee
Pritam, jest to dobra praktyka. Przed głosowaniem nad odpowiedzią proszę przeprowadzić odpowiednie badania
Neha Jain,
1
„use strict” ratuje złych programistów przed sobą. A ponieważ większość programistów jest złymi programistami, pomaga im to robić rzeczy, których zdecydowanie nie powinni robić, i kończy się w szybko tonącym bałaganie kodu.
MattE