MySQL Java JavaScript PHP Python HTML-CSS C-sharp

Închideri (closures) și funcâții IIFE

Închideri (closures)

Închiderea (closure) reprezintă o construcție în care o funcție, creată într-un anumit domeniu de vizibilitate, își amintește mediul său lexical chiar și atunci când este executată în afara domeniului său de vizibilitate.

Pentru a crea o închidere, sunt necesare trei componente:

  • Funcția externă, care definește un anumit domeniu de vizibilitate și în care sunt definite unele variabile - mediul lexical.
  • Variabilele (mediul lexical) definite în funcția externă.
  • Funcția imbricată, care utilizează aceste variabile.
function externa() {       // funcția externă
   let n;                  // o variabilă
   return function interna() {  // funcția imbricată
       // acțiuni cu variabila n
   };
}

Practic, funcția interna reprezintă în acest caz închiderea, care își amintește mediul său lexical - variabila n.

Să analizăm închiderile printr-un exemplu simplu:

function outer(){
   let x = 5;
   function inner(){
       x++;
       console.log(x);
   };
   return inner;
}
const fn = outer(); // fn = inner, deoarece funcția outer returnează funcția inner
// apelăm funcția internă inner
fn();   // 6
fn();   // 7
fn();   // 8

Aici, funcția outer stabilește domeniul său de vizibilitate în care sunt definite funcția internă inner și variabila x. Variabila x reprezintă mediul lexical pentru funcția inner. În funcția inner, incrementăm variabila x și afișăm valoarea sa în consolă. La final, funcția outer returnează funcția inner.

Apoi, apelăm funcția outer:

const fn = outer();

Întrucât funcția outer returnează funcția inner, atunci constanta fn va stoca o referință către funcția inner. Această funcție își amintește mediul său lexical, adică variabila externă x.

Ulterior, apelăm efectiv funcția inner de trei ori, iar observăm că variabila x, definită în afara funcției inner, este incrementată:

fn();   // 6
fn();   // 7
fn();   // 8

Chiar dacă variabila x este definită în afara funcției inner, aceasta funcție își amintește mediul său și poate să îl utilizeze, chiar dacă este apelată în afara funcției outer, în care a fost inițial definită. Acesta este esența închiderilor (closures).

În fiecare copie a închiderii se definește propria copie a mediului lexical:

// definim un obiect spațiu de nume
function outer(){
   let x = 5;
   function inner(){
       x++;
       console.log(x);
   };
   return inner;
}
const fn1 = outer();
const fn2 = outer();
fn1();  // 6
fn1();  // 7
fn2();  // 6
fn2();  // 7

Aici se observă că pentru fn1 și fn2 există propria lor copie a variabilei x, asupra căreia operează independent una de cealaltă.

Să luăm în considerare încă un exemplu:

function multiply(n){
   let x = n;
   return function(m){ return x * m;};
}
const fn1 = multiply(5);
const result1 = fn1(6); // 30
console.log(result1); // 30

const fn2= multiply(4);
const result2 = fn2(6); // 24
console.log(result2); // 24

Desigur, iată traducerea textului:

Așadar, aici apelul funcției multiply() duce la apelul unei alte funcții interne. Funcția internă:

function(m){ return x * m;};

Reține mediul în care a fost creată, în special valoarea variabilei x.

În cele din urmă, la apelul funcției multiply se definește constanta fn1, care reprezintă un closure, adică combină două lucruri: o funcție și mediul în care funcția a fost creată. Mediul constă în orice variabilă locală care exista în domeniul de aplicare al funcției multiply în momentul creării closure-ului.

Cu alte cuvinte, fn1 este un closure care conține atât funcția internă function(m){ return x * m;}, cât și variabila x, care a existat în momentul creării closure-ului.

La crearea a două closure-uri, fn1 și fn2, pentru fiecare dintre acestea se creează propria lor mediu.

Este important să nu ne confundăm în privința parametrilor. La definirea closure-ului:

const fn1 = multiply(5);

Numărul 5 este transmis pentru parametrul n al funcției multiply.

La apelul funcției interne:

const result1 = fn1(6);

Numărul 6 este transmis pentru parametrul m al funcției interne function(m){ return x * m;}.

De asemenea, putem utiliza și o altă variantă pentru apelarea closure-ului:

function multiply(n){
   let x = n;
   return function(m){ return x * m;};
}
const result = multiply(5)(6); // 30
console.log(result);

Funcțiile auto-invocate

De obicei, definirea unei funcții este separată de apelul ei: mai întâi definim funcția și apoi o apelăm. Dar acest lucru nu este obligatoriu. Putem crea, de asemenea, funcții care vor fi apelate imediat la momentul definirii. Aceste funcții sunt numite și Immediately Invoked Function Expression sau IIFE. Funcțiile de acest fel sunt încadrate în paranteze, iar după definirea funcției, în paranteze sunt transmise parametri:

// funcție auto-invocată
(function(){
   console.log("Salut, lume!");
}());

Aici, acest mic program poate fi împărțit în mai multe părți. În paranteze este definită o funcție anonimă:

function(){
   console.log("Salut, lume!");
}

Este o funcție obișnuită care afișează un șir de caractere. Dar și în paranteze, după definirea funcției, urmează paranteze goale:

function(){
   console.log("Salut, lume!");
}()

Aceste paranteze reprezintă aceleași paranteze care sunt folosite la apelul funcției și în care sunt plasate valorile pentru parametrii funcției. Cu toate acestea, funcția noastră anonimă nu are parametri, așa că parantezele sunt goale. Cu alte cuvinte, aici avem de fapt apelul funcției imediat după definirea ei. Și întreaga construcție este închisă în paranteze.

În final, definim o funcție anonimă și o apelăm imediat.

În mod similar, putem crea și apela o funcție care primește parametri:

(function (a, b){
     
   const result = a + b;
   console.log(${a} + ${b} = ${result});
}(4, 5));

Aici, funcția primește doi parametri, ale căror valori sunt adunate, iar rezultatul este afișat pe consolă. Imediat după definirea funcției, îi transmitem două numere - 4 și 5. Prin urmare, la executarea programului, se va defini și imediat executa funcția care va calcula suma numerelor 4 și 5.

← Lecția anterioară Lecția următoare →