scope - Hva er hensikten med å pakke hele Javascript-filer inn i anonyme funksjoner som «(function(){ … })()»?


Jeg har lest mye Javascript i det siste, og jeg har lagt merke til at hele filen er pakket inn på følgende måte i .js-filene som skal importeres.

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

Hva er grunnen til å gjøre dette fremfor et enkelt sett med konstruktørfunksjoner?

Svar #1

Det er vanligvis for å definere et navneområde (se senere) og kontrollere synligheten til medlemsfunksjoner og/eller variabler. Tenk på det som en objektdefinisjon. Det tekniske navnet på det er en Immediately Invoked Function Expression (IIFE). jQuery-plugins skrives vanligvis på denne måten.

I Javascript kan du nestle funksjoner. Så, følgende er lovlig:

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

Nå kan du kalle outerFunction(), men synligheten til innerFunction() er begrenset til omfanget av outerFunction(), noe som betyr at den er privat for outerFunction(). Det følger i hovedsak samme prinsipp som variabler i Javascript:

var globalVariable;

function someFunction() {
   var localVariable;
}

Tilsvarende:

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

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

I scenariet ovenfor kan du kalle globalFunction() fra hvor som helst, men du kan ikke kalle localFunction1 eller localFunction2.

Det du gjør når du skriver (function() { ... })(), er at du gjør koden inni den første parentesen til en funksjonsliteral (som betyr at hele "objektet" faktisk er en funksjon). Etter det kaller du selv funksjonen (den siste ()) som du nettopp definerte. Så den største fordelen med dette, som jeg nevnte tidligere, er at du kan ha private metoder/funksjoner og egenskaper:

(function() {
   var private_var;

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

I det første eksempelet ville du eksplisitt kalt globalFunction ved navn for å kjøre den. Det vil si, du ville bare gjort globalFunction() for å kjøre den. Men i eksempelet ovenfor definerer du ikke bare en funksjon; du definerer og kaller den i ett og samme steg. Dette betyr at når JavaScript-filen din lastes, blir den umiddelbart utført. Selvfølgelig kunne du gjort:

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

Oppførselen ville stort sett være den samme bortsett fra én betydelig forskjell: du unngår å forurense det globale området når du bruker en IIFE (som en konsekvens betyr det også at du ikke kan kalle funksjonen flere ganger siden den ikke har et navn, men siden denne funksjonen bare er ment å bli kjørt én gang, er det egentlig ikke et problem).

Det kule med IIFEs er at du også kan definere ting inni og bare eksponere de delene du ønsker til omverdenen, så (et eksempel på navnerom slik at du i utgangspunktet kan lage ditt eget bibliotek/plugin):

var myPlugin = (function() {
 var private_var;

 function private_function() {
 }

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

Nå kan du kalle myPlugin.public_function1(), men du kan ikke få tilgang til private_function()! Så veldig likt en klasse-definisjon. For å forstå dette bedre, anbefaler jeg følgende lenker for videre lesing:

  • Navngiving av ditt Javascript
  • Private medlemmer i Javascript (av Douglas Crockford)

REDIGER

Jeg glemte å nevne. I den siste (), kan du sende inn hva som helst inne. For eksempel, når du lager jQuery-plugins, sender du inn jQuery eller $ slik:

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

Så det du gjør her er å definere en funksjon som tar inn ett parameter (kalt jQ, en lokal variabel, og kjent bare for den funksjonen). Deretter kaller du funksjonen på seg selv og sender inn et parameter (også kalt jQuery, men denne er fra omverdenen og en referanse til selve jQuery). Det er ikke et presserende behov for å gjøre dette, men det finnes noen fordeler:

  • Du kan omdefinere en global parameter og gi den et navn som gir mening i det lokale området.
  • Det er en liten ytelsesfordel siden det er raskere å slå opp ting i det lokale området i stedet for å måtte gå opp i områdekjeden til det globale området.
  • Det er fordeler med komprimering (minifisering).

Tidligere beskrev jeg hvordan disse funksjonene kjører automatisk ved oppstart, men hvis de kjører automatisk, hvem gir da argumentene? Denne teknikken antar at alle parameterne du trenger allerede er definert som globale variabler. Så hvis jQuery ikke allerede var definert som en global variabel, ville ikke dette eksemplet fungere. Som du kanskje kan gjette, gjør jquery.js under sin initialisering én ting, nemlig å definere en 'jQuery' global variabel, samt den mer kjente '$' globale variabelen, som gjør at denne koden fungerer etter at jQuery er inkludert.

Svar #2

Kort sagt

Sammendrag

I sin enkleste form har denne teknikken som mål å pakke kode inn i et funksjonsområde.

Det bidrar til å redusere sjansene for:

  • konflikter med andre applikasjoner/biblioteker
  • forurensende overlegen (global mest sannsynlig) omfang

Det oppdager ikke når dokumentet er klart - det er ikke en slags document.onload eller window.onload

Det er vanligvis kjent som en Umiddelbart Kjørt Funksjonsuttrykk (IIFE) eller Selvutførende Anonym Funksjon.

Kode forklart

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

I eksemplet ovenfor vil enhver variabel som er definert i funksjonen (dvs. erklært ved bruk av var) være «privat» og tilgjengelig kun innenfor funksjonens omfang (som Vivin Paliath sier). Med andre ord, disse variablene er ikke synlige/tilgjengelige utenfor funksjonen. Se live demo.

Javascript har funksjonsskoping. "Parametere og variabler definert i en funksjon er ikke synlige utenfor funksjonen, og en variabel definert hvor som helst i en funksjon er synlig overalt i funksjonen." (fra "Javascript: The Good Parts").

Mer detaljer

Alternativ kode

Til slutt kunne koden som ble lagt ut tidligere også gjøres på følgende måte:

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

Se direktesendt demo.

Røttene

Iterasjon 1

En dag tenkte sannsynligvis noen «det må finnes en måte å unngå å navngi 'myMainFunction', siden alt vi vil er å kjøre den med en gang.»

Hvis du går tilbake til det grunnleggende, finner du ut at:

  • uttrykk: noe som evalueres til en verdi. f.eks. 3+11/x
  • setning: linje(r) med kode som gjør noe MEN den evaluerer ikke til en verdi. f.eks. if(){}

På samme måte evalueres funksjonsuttrykk til en verdi. Og en konsekvens (antar jeg?) er at de kan kalles umiddelbart:

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

Så vårt mer komplekse eksempel blir:

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

Se direktesendt demo.

Iterasjon 2

Neste steg er tanken «hvorfor ha var myMainFunction = hvis vi ikke engang bruker det!?».

Svaret er enkelt: prøv å fjerne dette, som vist nedenfor:

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

Se direktesendt demo.

Det vil ikke fungere fordi "funksjonsdeklarasjoner ikke kan kalles".

Trikset er at ved å fjerne var myMainFunction = forvandlet vi funksjonsuttrykket til en funksjonsdeklarasjon. Se lenkene i "Ressurser" for mer informasjon om dette.

Neste spørsmål er «hvorfor kan jeg ikke beholde det som et funksjonsuttrykk med noe annet enn var myMainFunction =?」

Svaret er "du kan", og det finnes faktisk mange måter du kan gjøre dette på: legge til en +, en !, en -, eller kanskje omslutte med et par parenteser (som det nå gjøres av konvensjon), og mer, tror jeg. Som eksempel:

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

eller

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

eller

 -function(){ console.log('mamamia!'); }(); // live demo: jsbin.com/wejupaheva/1/edit?js,console
  • Hva gjør utropstegnet foran funksjonen?
  • JavaScript pluss-tegn foran funksjonsnavn

Så når den relevante modifikasjonen legges til det som en gang var vår "Alternative Code", går vi tilbake til nøyaktig den samme koden som den som ble brukt i eksemplet "Code Explained"

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

Les mer om Uttrykk vs Utsagn:

  • developer.mozilla.org/nb-NO/docs/Web/JavaScript/Guide/Expressions_and_Operators
  • developer.mozilla.org/no/docs/Web/JavaScript/Reference/Functions#Funksjonskonstruktør_vs._funksjonsdeklarasjon_vs._funksjonsuttrykk
  • Javascript: forskjellen mellom en setning og et uttrykk?
  • Uttrykk versus setning

Avmystifisere scopes

En ting man kan undre seg over er "hva skjer når man IKKE definerer variabelen 'riktig' inne i funksjonen - altså gjør en enkel tildeling i stedet?"

(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

Se direktesendt demo.

I utgangspunktet, hvis en variabel som ikke ble deklarert i sitt nåværende omfang får tildelt en verdi, så "forekommer en oppslag i omfangskjeden til den finner variabelen eller når det globale omfanget (på hvilket tidspunkt vil den opprette den)".

Når man er i et nettlesermiljø (vs et servermiljø som nodejs) er det globale omfanget definert av window-objektet. Derfor kan vi gjøre window.myOtherFunction().

Mitt tips om "Gode praksiser" om dette emnet er å alltid bruke var når man definerer noe: enten det er et tall, objekt eller funksjon, & selv når det er i globalt omfang. Dette gjør koden mye enklere.

Merk:

  • javascript har ikke blokk omfang (Oppdatering: blokk-omfang lokale variabler lagt til i ES6.)
  • javascript har bare funksjonsområde & globalt område (window-område i et nettlesermiljø)

Les mer om Javascript Scopes:

  • Hva er hensikten med nøkkelordet var, og når bør det brukes (eller utelates)?
  • Hva er omfanget av variabler i JavaScript?

Ressurser

  • youtu.be/i_qE1iAmjFg?t=2m15s - Paul Irish presenterer IIFE på minutt 2:15, se dette!
  • developer.mozilla.org/no/docs/Web/JavaScript/Referanse/Funksjoner
  • Bok: Javascript, de gode delene - sterkt anbefalt
  • youtu.be/i_qE1iAmjFg?t=4m36s - Paul Irish presenterer modulmønsteret kl. 4:36

Neste steg

Når du først forstår dette IIFE-konseptet, fører det til module pattern, som ofte gjøres ved å utnytte dette IIFE-mønsteret. Ha det gøy :)

Svar #3

Javascript i en nettleser har egentlig bare et par effektive omfang: funksjonsomfang og globalt omfang.

Hvis en variabel ikke er i funksjonsomfang, er den i globalt omfang. Og globale variabler er generelt dårlige, så dette er en konstruksjon for å holde en biblioteks variabler for seg selv.

Svar #4

Det kalles en closure. Det forsegler i praksis koden inne i funksjonen slik at andre biblioteker ikke forstyrrer den. Det ligner på å lage et navnerom i kompilerte språk.

Eksempel. Anta at jeg skriver:

(function() {

    var x = 2;

    // do stuff with x

})();

Nå kan ikke andre biblioteker få tilgang til variabelen x jeg opprettet for å bruke i biblioteket mitt.

Relaterte artikler