Išnagrinėkite JavaScript uždarąsias sritis (closures) pasitelkdami praktinius pavyzdžius, suprasdami, kaip jos veikia ir kaip pritaikomos realioje programinės įrangos kūrimo praktikoje.
JavaScript uždarosios sritys (Closures): paaiškinimas su praktiniais pavyzdžiais
Uždarosios sritys (closures) yra fundamentalus JavaScript konceptas, kuris dažnai kelia sumaištį įvairaus lygio programuotojams. Suprasti uždarąsias sritis yra itin svarbu norint rašyti efektyvų, prižiūrimą ir saugų kodą. Šis išsamus vadovas paaiškins uždarąsias sritis su praktiniais pavyzdžiais ir parodys jų pritaikymą realiame pasaulyje.
Kas yra uždaroji sritis (Closure)?
Paprasčiau tariant, uždaroji sritis yra funkcijos ir leksinės aplinkos, kurioje ta funkcija buvo deklaruota, derinys. Tai reiškia, kad uždaroji sritis leidžia funkcijai pasiekti kintamuosius iš ją supančios apimties srities, net ir pasibaigus išorinės funkcijos vykdymui. Galima įsivaizduoti, kad vidinė funkcija „prisimena“ savo aplinką.
Norint tai tikrai suprasti, išskaidykime pagrindinius komponentus:
- Funkcija: Vidinė funkcija, kuri sudaro uždarosios srities dalį.
- Leksinė aplinka: Supanti apimties sritis, kurioje funkcija buvo deklaruota. Tai apima kintamuosius, funkcijas ir kitas deklaracijas.
Magija įvyksta todėl, kad vidinė funkcija išlaiko prieigą prie kintamųjų savo leksinėje apimties srityje, net ir po to, kai išorinė funkcija grąžina reikšmę. Šis elgesys yra esminė dalis to, kaip JavaScript tvarko apimties sritis ir atminties valdymą.
Kodėl uždarosios sritys yra svarbios?
Uždarosios sritys nėra tik teorinis konceptas; jos yra būtinos daugeliui įprastų programavimo šablonų JavaScript. Jos suteikia šiuos privalumus:
- Duomenų inkapsuliacija: Uždarosios sritys leidžia kurti privačius kintamuosius ir metodus, apsaugant duomenis nuo išorinės prieigos ir modifikavimo.
- Būsenos išsaugojimas: Uždarosios sritys išlaiko kintamųjų būseną tarp funkcijos iškvietimų, kas yra naudinga kuriant skaitiklius, laikmačius ir kitus būseną turinčius komponentus.
- Aukštesnės eilės funkcijos: Uždarosios sritys dažnai naudojamos kartu su aukštesnės eilės funkcijomis (funkcijomis, kurios priima kitas funkcijas kaip argumentus arba grąžina funkcijas), leidžiant kurti galingą ir lankstų kodą.
- Asinchroninis JavaScript: Uždarosios sritys atlieka lemiamą vaidmenį valdant asinchronines operacijas, tokias kaip atgalinio iškvietimo funkcijos (callbacks) ir pažadai (promises).
Praktiniai JavaScript uždarųjų sričių pavyzdžiai
Panagrinėkime keletą praktinių pavyzdžių, kad iliustruotume, kaip veikia uždarosios sritys ir kaip jas galima naudoti realaus pasaulio scenarijuose.
1 pavyzdys: Paprastas skaitiklis
Šis pavyzdys parodo, kaip uždaroji sritis gali būti naudojama kuriant skaitiklį, kuris išlaiko savo būseną tarp funkcijos iškvietimų.
function createCounter() {
let count = 0;
return function() {
count++;
console.log(count);
};
}
const increment = createCounter();
increment(); // Output: 1
increment(); // Output: 2
increment(); // Output: 3
Paaiškinimas:
createCounter()
yra išorinė funkcija, kuri deklaruoja kintamąjįcount
.- Ji grąžina vidinę funkciją (šiuo atveju anoniminę funkciją), kuri padidina
count
ir išveda jo reikšmę į konsolę. - Vidinė funkcija suformuoja uždarąją sritį aplink kintamąjį
count
. - Net ir pasibaigus
createCounter()
vykdymui, vidinė funkcija išlaiko prieigą prie kintamojocount
. - Kiekvienas
increment()
iškvietimas padidina tą patįcount
kintamąjį, demonstruodamas uždarosios srities gebėjimą išsaugoti būseną.
2 pavyzdys: Duomenų inkapsuliacija su privačiais kintamaisiais
Uždarosios sritys gali būti naudojamos kuriant privačius kintamuosius, apsaugant duomenis nuo tiesioginės prieigos ir modifikavimo iš funkcijos išorės.
function createBankAccount(initialBalance) {
let balance = initialBalance;
return {
deposit: function(amount) {
balance += amount;
return balance; //Returning for demonstration, could be void
},
withdraw: function(amount) {
if (amount <= balance) {
balance -= amount;
return balance; //Returning for demonstration, could be void
} else {
return "Insufficient funds.";
}
},
getBalance: function() {
return balance;
}
};
}
const account = createBankAccount(1000);
console.log(account.deposit(500)); // Output: 1500
console.log(account.withdraw(200)); // Output: 1300
console.log(account.getBalance()); // Output: 1300
// Trying to access balance directly will not work
// console.log(account.balance); // Output: undefined
Paaiškinimas:
createBankAccount()
sukuria banko sąskaitos objektą su metodais pinigų įnešimui, išėmimui ir balanso gavimui.- Kintamasis
balance
yra deklaruotascreateBankAccount()
apimties srityje ir nėra tiesiogiai pasiekiamas iš išorės. - Metodai
deposit
,withdraw
irgetBalance
suformuoja uždarąsias sritis aplink kintamąjįbalance
. - Šie metodai gali pasiekti ir modifikuoti kintamąjį
balance
, tačiau pats kintamasis lieka privatus.
3 pavyzdys: Uždarųjų sričių naudojimas su `setTimeout` cikle
Uždarosios sritys yra būtinos dirbant su asinchroninėmis operacijomis, tokiomis kaip setTimeout
, ypač cikluose. Be uždarųjų sričių galite susidurti su netikėtu elgesiu dėl asinchroninės JavaScript prigimties.
for (var i = 1; i <= 5; i++) {
(function(j) {
setTimeout(function() {
console.log("Value of i: " + j);
}, j * 1000);
})(i);
}
// Output:
// Value of i: 1 (after 1 second)
// Value of i: 2 (after 2 seconds)
// Value of i: 3 (after 3 seconds)
// Value of i: 4 (after 4 seconds)
// Value of i: 5 (after 5 seconds)
Paaiškinimas:
- Be uždarosios srities (iš karto iškviečiamos funkcijos išraiškos arba IIFE), visi
setTimeout
atgalinio iškvietimo funkcijos galiausiai nurodytų tą patį kintamąjįi
, kurio galutinė reikšmė po ciklo pabaigos būtų 6. - IIFE sukuria naują apimties sritį kiekvienai ciklo iteracijai, užfiksuodama dabartinę
i
reikšmęj
parametre. - Kiekviena
setTimeout
atgalinio iškvietimo funkcija suformuoja uždarąją sritį aplink kintamąjįj
, užtikrindama, kad ji išvestų teisingąi
reikšmę kiekvienai iteracijai.
Naudojant let
vietoje var
cikle taip pat išspręstų šią problemą, nes let
sukuria bloko apimties sritį kiekvienai iteracijai.
for (let i = 1; i <= 5; i++) {
setTimeout(function() {
console.log("Value of i: " + i);
}, i * 1000);
}
// Output (same as above):
// Value of i: 1 (after 1 second)
// Value of i: 2 (after 2 seconds)
// Value of i: 3 (after 3 seconds)
// Value of i: 4 (after 4 seconds)
// Value of i: 5 (after 5 seconds)
4 pavyzdys: Karifikavimas (Currying) ir dalinis pritaikymas
Uždarosios sritys yra pagrindas karifikavimui ir daliniam pritaikymui – technikoms, naudojamoms transformuoti funkcijas su keliais argumentais į funkcijų sekas, kurių kiekviena priima vieną argumentą.
function multiply(a) {
return function(b) {
return function(c) {
return a * b * c;
};
};
}
const multiplyBy5 = multiply(5);
const multiplyBy5And2 = multiplyBy5(2);
console.log(multiplyBy5And2(3)); // Output: 30 (5 * 2 * 3)
Paaiškinimas:
multiply
yra karifikuota funkcija, kuri priima tris argumentus, po vieną.- Kiekviena vidinė funkcija suformuoja uždarąją sritį aplink kintamuosius iš savo išorinės apimties srities (
a
,b
). multiplyBy5
yra funkcija, kuriojea
jau yra nustatyta į 5.multiplyBy5And2
yra funkcija, kuriojea
jau yra nustatyta į 5, ob
– į 2.- Galutinis iškvietimas
multiplyBy5And2(3)
užbaigia skaičiavimą ir grąžina rezultatą.
5 pavyzdys: Modulio šablonas (Module Pattern)
Uždarosios sritys plačiai naudojamos modulio šablone, kuris padeda organizuoti ir struktūrizuoti JavaScript kodą, skatinant moduliškumą ir išvengiant pavadinimų konfliktų.
const myModule = (function() {
let privateVariable = "Hello, world!";
function privateMethod() {
console.log(privateVariable);
}
return {
publicMethod: function() {
privateMethod();
},
publicProperty: "This is a public property."
};
})();
console.log(myModule.publicProperty); // Output: This is a public property.
myModule.publicMethod(); // Output: Hello, world!
// Trying to access privateVariable or privateMethod directly will not work
// console.log(myModule.privateVariable); // Output: undefined
// myModule.privateMethod(); // Output: TypeError: myModule.privateMethod is not a function
Paaiškinimas:
- IIFE sukuria naują apimties sritį, inkapsuliuodama
privateVariable
irprivateMethod
. - Grąžinamas objektas atskleidžia tik
publicMethod
irpublicProperty
. publicMethod
suformuoja uždarąją sritį aplinkprivateMethod
irprivateVariable
, leisdama jai juos pasiekti net ir po IIFE įvykdymo.- Šis šablonas efektyviai sukuria modulį su privačiais ir viešais nariais.
Uždarosios sritys ir atminties valdymas
Nors uždarosios sritys yra galingos, svarbu žinoti apie jų galimą poveikį atminties valdymui. Kadangi uždarosios sritys išlaiko prieigą prie kintamųjų iš jas supančios apimties srities, jos gali neleisti šiems kintamiesiems būti surinktiems šiukšlių surinkėjo (garbage collector), jei jie nebėra reikalingi. Tai gali sukelti atminties nutekėjimą, jei su tuo elgiamasi neatsargiai.
Kad išvengtumėte atminties nutekėjimo, užtikrinkite, kad nutrauktumėte visas nereikalingas nuorodas į kintamuosius uždarosiose srityse, kai jos nebėra reikalingos. Tai galima padaryti nustatant kintamųjų reikšmę į null
arba pertvarkant kodą, kad išvengtumėte nereikalingų uždarųjų sričių kūrimo.
Dažniausios klaidos su uždarosiomis sritimis, kurių reikia vengti
- Leksinės apimties srities pamiršimas: Visada prisiminkite, kad uždaroji sritis užfiksuoja aplinką *jos sukūrimo metu*. Jei kintamieji pasikeičia po uždarosios srities sukūrimo, uždaroji sritis atspindės tuos pakeitimus.
- Nereikalingų uždarųjų sričių kūrimas: Venkite kurti uždarąsias sritis, jei jos nėra reikalingos, nes jos gali paveikti našumą ir atminties naudojimą.
- Kintamųjų nutekinimas: Būkite atidūs uždarųjų sričių užfiksuotų kintamųjų gyvavimo laikui ir užtikrinkite, kad jie būtų atlaisvinti, kai nebėra reikalingi, siekiant išvengti atminties nutekėjimo.
Išvada
JavaScript uždarosios sritys yra galingas ir esminis konceptas, kurį turi suprasti kiekvienas JavaScript programuotojas. Jos įgalina duomenų inkapsuliaciją, būsenos išsaugojimą, aukštesnės eilės funkcijas ir asinchroninį programavimą. Suprasdami, kaip veikia uždarosios sritys ir kaip jas efektyviai naudoti, galite rašyti efektyvesnį, prižiūrimą ir saugesnį kodą.
Šiame vadove pateikta išsami uždarųjų sričių apžvalga su praktiniais pavyzdžiais. Praktikuodamiesi ir eksperimentuodami su šiais pavyzdžiais, galite pagilinti savo supratimą apie uždarąsias sritis ir tapti labiau patyrusiu JavaScript programuotoju.
Tolimesniam mokymuisi
- Mozilla Developer Network (MDN): Uždarosios sritys (Closures) - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures
- You Don't Know JS: Scope & Closures by Kyle Simpson
- Naršykite internetines kodavimo platformas, tokias kaip CodePen ir JSFiddle, kad eksperimentuotumėte su skirtingais uždarųjų sričių pavyzdžiais.