JavaScript to język, który oferuje niesamowite możliwości. Jedną z funkcji, która często budzi emocje wśród programistów, są domknięcia.
Dla początkujących może wydawać się skomplikowane ale w tym artykule wyjaśnię Ci, czym są domknięcia w JavaScript, jak działają i dlaczego są tak ważne.
Będziemy używać prostych przykładów, które pomogą Ci zrozumieć ten temat i zastosować go w Twoim kodzie.
Czym są domknięcia w JavaScript?
Na początek najważniejsze – czym są domknięcia w JavaScript?
Domknięcie (z ang. closure) to mechanizm, w którym funkcja wewnętrzna ma dostęp do zmiennych zdefiniowanych w funkcji zewnętrznej, nawet po zakończeniu jej wykonywania.
Daje to niezwykłą moc, bo możesz „domknąć” pewne dane w ramach funkcji i korzystać z nich, kiedy tego potrzebujesz.
Wyobraź sobie, że masz funkcję, która tworzy zestaw danych i zwraca inną funkcję, która może korzystać z tych danych.
Zmienna zewnętrzna zostaje „domknięta” i zachowana dla funkcji wewnętrznej, nawet po zakończeniu działania funkcji zewnętrznej.
Jak działa domknięcie w JavaScript?
Przykład z domknięciem
Spróbujmy zrozumieć to za pomocą prostego przykładu.
Zobaczmy, jak działa domknięcie:
function outerFunction() {
let outerVariable = "Jestem na zewnątrz!";
function innerFunction() {
console.log(outerVariable); // 'Jestem na zewnątrz!'
}
return innerFunction;
}
const myClosure = outerFunction();
myClosure();
Mamy tutaj dwie funkcje – outerFunction
i innerFunction
. Kiedy wywołujemy outerFunction
, zwraca ona wewnętrzną funkcję.
Wewnętrzna funkcja innerFunction
wciąż ma dostęp do zmiennej outerVariable
, nawet po zakończeniu działania outerFunction
.
To właśnie jest domknięcie – funkcja zapamiętuje swoje otoczenie (czyli zmienne, które były dostępne w momencie jej tworzenia).
Lexical Scope – podstawa domknięć
Aby zrozumieć, jak działają domknięcia, musisz poznać pojęcie zakresu leksykalnego (ang. lexical scope).
W JavaScript zmienne mają zakres określony przez to, gdzie zostały zdefiniowane, a nie gdzie zostały wywołane.
To oznacza, że wewnętrzna funkcja ma dostęp do zmiennych z funkcji zewnętrznej, ponieważ została w niej zdefiniowana.
Przykład:
function sayHello() {
let name = "Janek";
if (true) {
let name = "Adam";
console.log(name); // "Adam"
}
console.log(name); // "Janek"
}
sayHello();
W tym przykładzie mamy dwie zmienne o nazwie name
, ale każda z nich działa w swoim własnym zakresie leksykalnym.
Zmienna wewnątrz bloku if
nie wpływa na zmienną name
zdefiniowaną w zakresie głównym funkcji sayHello
.
Różnica między var
, let
i const
W JavaScript mamy trzy słowa kluczowe do deklarowania zmiennych: var
, let
i const
.
Zmienna zadeklarowana za pomocą var
może być dostępna poza blokiem, w którym została zadeklarowana.
Natomiast zmienne zdefiniowane za pomocą let
i const
mają zakres blokowy, co oznacza, że są dostępne tylko wewnątrz tego bloku.
Przykład z var
:
function testVar() {
var licznik = 1;
if (true) {
var licznik = 2;
console.log(licznik); // 2
}
console.log(licznik); // 2
}
testVar();
Przykład z let
:
function testLet() {
let licznik = 1;
if (true) {
let licznik = 2;
console.log(licznik); // 2
}
console.log(licznik); // 1
}
testLet();
Jak widzisz, let
i const
zapewniają większą kontrolę nad zakresem leksykalnym, co czyni kod bardziej przewidywalnym i bezpiecznym.
Jak wykorzystać domknięć w praktyce?
Tworzenie liczników
Domknięcia są świetne do tworzenia liczników, które mogą domknąć zmienną licznikową i stopniowo ją zwiększać:
function createCounter() {
let count = 0;
return function () {
count++;
console.log(count);
};
}
const counter = createCounter();
counter(); // 1
counter(); // 2
counter(); // 3
Tutaj funkcja createCounter
tworzy licznik, który domyka zmienną count
.
Za każdym razem, gdy wywołasz counter()
, wartość count
zostanie zwiększona.
Domknięcia jako prywatne zmienne
Jednym z najciekawszych zastosowań domknięć jest tworzenie prywatnych zmiennych.
Możemy używać funkcji do domknięcia zmiennych, które nie są dostępne z zewnątrz:
function privateData() {
let secret = "Moja tajemnica";
return {
getSecret: function () {
return secret;
},
setSecret: function (newSecret) {
secret = newSecret;
},
};
}
const myData = privateData();
console.log(myData.getSecret()); // 'Moja tajemnica'
myData.setSecret("Nowa tajemnica");
console.log(myData.getSecret()); // 'Nowa tajemnica'
Tutaj mamy funkcję privateData
, która domyka zmienną secret
, tworząc mechanizm prywatnych zmiennych z ograniczonym dostępem.
Pułapki związane z domknięciami
Chociaż domknięcia są świetne, mogą też prowadzić do problemów, zwłaszcza gdy używasz ich niewłaściwie.
Jednym z najczęstszych błędów jest zła obsługa zmiennych w pętli.
Przykład z var
:
for (var i = 0; i < 3; i++) {
setTimeout(function () {
console.log(i);
}, 1000);
}
// Output: 3, 3, 3
Zmienna i
jest domknięta i po zakończeniu pętli ma wartość 3.
Dlatego wszystkie wywołania setTimeout
logują tę wartość. Rozwiązanie?
Użyj let
, który domknie zmienną dla każdego wywołania:
for (let i = 0; i < 3; i++) {
setTimeout(function () {
console.log(i);
}, 1000);
}
// Output: 0, 1, 2
Wydajność i zarządzanie pamięcią
Domknięcia w JavaScript mogą czasem prowadzić do problemów z pamięcią, jeśli używasz ich w nieodpowiedni sposób.
Gdy tworzysz domknięcie, zmienne zewnętrzne są zachowywane w pamięci, dopóki są one potrzebne.
Jeśli masz zbyt wiele domknięć lub zbyt duże domknięcia, może to prowadzić do niepotrzebnego obciążenia pamięci.
Przykład:
function outerFunction() {
var hugeArray = new Array(1000000).fill("Dane");
function innerFunction() {
console.log(hugeArray.length);
}
return innerFunction;
}
const myClosure = outerFunction();
myClosure();
Tutaj duża tablica hugeArray
jest domknięta, co oznacza, że zostanie ona zachowana w pamięci, dopóki innerFunction
będzie miała do niej dostęp.
Aby tego uniknąć, możesz domknąć tylko to, co jest naprawdę istotne, i usuwać referencje, gdy nie są już potrzebne.
Zastosowanie domknięć w node.js i express.js
Domknięcia są kluczowe również w środowiskach takich jak Node.js, gdzie często mamy do czynienia z asynchronicznymi operacjami.
Dzięki domknięciom możemy mieć pewność, że nasze funkcje asynchroniczne będą miały dostęp do odpowiednich zmiennych, nawet gdy zostaną wykonane po pewnym czasie.
Przykład z express.js
:
const express = require("express");
const app = express();
function createLogger() {
const start = Date.now();
return function (req, res, next) {
const duration = Date.now() - start;
console.log(`Request time: ${duration}ms`);
next();
};
}
app.use(createLogger());
app.get("/", (req, res) => {
res.send("Hello World!");
});
app.listen(3000);
W tym przypadku funkcja createLogger
domyka zmienną start
, która zachowuje czas rozpoczęcia żądania.
Dzięki temu każda funkcja middleware ma dostęp do tej wartości.
Podsumowanie
Domknięcia w JavaScript pozwalają domknąć zmienne w funkcjach i mieć do nich dostęp w odpowiednim momencie.
Dzięki zakresowi leksykalnemu w JavaScript, możesz tworzyć bardziej złożone aplikacje, które działają przewidywalnie i efektywnie.
Pamiętaj jednak, aby używać domknięć rozważnie – nadmierne korzystanie może prowadzić do problemów z wydajnością.