Tutustu JavaScriptin WeakMap- ja WeakSet-ominaisuuksiin tehokkaan muistinhallinnan kannalta. Opi, kuinka nämä kokoelmat vapauttavat automaattisesti käyttämätöntä muistia, mikä parantaa suorituskykyä monimutkaisissa sovelluksissa.
JavaScript WeakMap ja WeakSet: Tehokkaan muistinhallinnan hallinta
JavaScript tarjoaa useita sisäänrakennettuja tietorakenteita tietokokoelmien hallintaan. Vaikka tavalliset Map ja Set tarjoavat tehokkaita työkaluja, ne voivat joskus johtaa muistivuotoihin, erityisesti monimutkaisissa sovelluksissa. Tässä kohtaa WeakMap ja WeakSet tulevat mukaan kuvaan. Nämä erikoistuneet kokoelmat tarjoavat ainutlaatuisen lähestymistavan muistinhallintaan, jolloin JavaScriptin roskienkerääjä voi vapauttaa muistia tehokkaammin.
Ongelman ymmärtäminen: Vahvat viittaukset
Ennen kuin sukellamme WeakMap- ja WeakSet-ominaisuuksiin, ymmärretään perusongelma: vahvat viittaukset. JavaScriptissä, kun objekti on tallennettu avaimena Map-muuttujaan tai arvona Set-muuttujaan, kokoelma ylläpitää vahvaa viittausta kyseiseen objektiin. Tämä tarkoittaa, että niin kauan kuin Map tai Set on olemassa, roskienkerääjä ei voi vapauttaa objektin käyttämää muistia, vaikka objektiin ei viitattaisi enää missään muualla koodissasi. Tämä voi johtaa muistivuotoihin, erityisesti kun käsitellään suuria tai pitkäikäisiä kokoelmia.
Harkitse tätä esimerkkiä:
let myMap = new Map();
let key = { id: 1, name: "Esimerkkiobjekti" };
myMap.set(key, "Jokin arvo");
// Vaikka 'key'-arvoa ei enää käytetä suoraan...
key = null;
// ...Map pitää silti viittausta siihen.
console.log(myMap.size); // Tuloste: 1
Tässä tapauksessa, vaikka key-arvo asetetaan null-arvoon, Map pitää silti viittausta alkuperäiseen objektiin. Roskienkerääjä ei voi vapauttaa kyseisen objektin käyttämää muistia, koska Map estää sen.
Esittelyssä WeakMap ja WeakSet: Heikot viittaukset apuun
WeakMap ja WeakSet ratkaisevat tämän ongelman käyttämällä heikkoja viittauksia. Heikko viittaus mahdollistaa objektin roskienkeräämisen, jos siihen ei ole muita vahvoja viittauksia. Kun WeakMap-avaimeen tai WeakSet-arvoon viitataan vain heikosti, roskienkerääjä voi vapaasti vapauttaa muistin. Kun objekti on roskienkerätty, vastaava merkintä poistetaan automaattisesti WeakMap- tai WeakSet-muuttujasta.
WeakMap: Avain-arvo -parit, joissa on heikot avaimet
WeakMap on avain-arvo -parien kokoelma, jossa avaimien on oltava objekteja. Avaimet pidetään heikkoina, mikä tarkoittaa, että jos avainobjektiin ei enää viitata muualla, se voidaan roskienkerätä, ja vastaava merkintä WeakMap-muuttujassa poistetaan. Arvot sen sijaan pidetään normaaleilla (vahvoilla) viittauksilla.
Tässä on perusesimerkki:
let weakMap = new WeakMap();
let key = { id: 1, name: "WeakMap-avain" };
let value = "Liittyvät tiedot";
weakMap.set(key, value);
console.log(weakMap.get(key)); // Tuloste: "Liittyvät tiedot"
key = null;
// Roskienkeräyksen jälkeen (jonka ei taata tapahtuvan heti)...
// weakMap.get(key) saattaa palauttaa määrittämättömän. Tämä on toteutuskohtaista.
// Emme voi suoraan havaita, milloin merkintä poistetaan WeakMap-muuttujasta, mikä on suunniteltu niin.
Tärkeimmät erot Map-muuttujasta:
- Avainten on oltava objekteja: Vain objekteja voidaan käyttää avaimina
WeakMap-muuttujassa. Alkuarvot (merkkijonot, numerot, totuusarvot, symbolit) eivät ole sallittuja. Tämä johtuu siitä, että alkuarvot ovat muuttumattomia eivätkä vaadi roskienkeräystä samalla tavalla kuin objektit. - Ei iteraatiota: Et voi iteroida
WeakMap-avaimia, -arvoja tai -merkintöjä. Ei ole olemassa menetelmiä, kutenforEach,keys(),values()taientries(). Tämä johtuu siitä, että näiden menetelmien olemassaolo edellyttäisi, ettäWeakMapylläpitää vahvaa viittausta avaimiinsa, mikä mitätöisi heikkojen viittausten tarkoituksen. - Ei koko-ominaisuutta:
WeakMap-muuttujalla ei olesize-ominaisuutta. Koon määrittäminen edellyttäisi myös avainten iterointia, mikä ei ole sallittua. - Rajoitetut menetelmät:
WeakMaptarjoaa vainget(key),set(key, value),has(key)jadelete(key).
WeakSet: Kokoelma heikosti pidettyjä objekteja
WeakSet on samanlainen kuin Set, mutta se sallii vain objektien tallentamisen arvoiksi. Kuten WeakMap, WeakSet pitää näitä objekteja heikkoina. Jos WeakSet-objektiin ei enää viitata vahvasti muualla, se voidaan roskienkerätä, ja WeakSet poistaa automaattisesti objektin.
Tässä on yksinkertainen esimerkki:
let weakSet = new WeakSet();
let obj1 = { id: 1, name: "Objekti 1" };
let obj2 = { id: 2, name: "Objekti 2" };
weakSet.add(obj1);
weakSet.add(obj2);
console.log(weakSet.has(obj1)); // Tuloste: true
obj1 = null;
// Roskienkeräyksen jälkeen (ei taata heti)...
// weakSet.has(obj1) saattaa palauttaa false-arvon. Tämä on toteutuskohtaista.
// Emme voi suoraan havaita, milloin elementti poistetaan WeakSet-muuttujasta.
Tärkeimmät erot Set-muuttujasta:
- Arvojen on oltava objekteja: Vain objekteja voidaan tallentaa
WeakSet-muuttujaan. Alkuarvot eivät ole sallittuja. - Ei iteraatiota: Et voi iteroida
WeakSet-muuttujaa. Ei ole olemassaforEach-menetelmää tai muita keinoja käyttää elementtejä. - Ei koko-ominaisuutta:
WeakSet-muuttujalla ei olesize-ominaisuutta. - Rajoitetut menetelmät:
WeakSettarjoaa vainadd(value),has(value)jadelete(value).
Käytännön käyttötapaukset WeakMap- ja WeakSet-muuttujille
WeakMap- ja WeakSet-muuttujien rajoitukset saattavat tehdä niistä vähemmän monipuolisia kuin niiden vahvemmat vastineet. Niiden ainutlaatuiset muistinhallintaominaisuudet tekevät niistä kuitenkin korvaamattomia tietyissä tilanteissa.
1. DOM-elementin metatiedot
Yleinen käyttötapaus on metatietojen liittäminen DOM-elementteihin saastuttamatta DOM:ia. Haluat ehkä esimerkiksi tallentaa komponenttikohtaisia tietoja, jotka on liitetty tiettyyn HTML-elementtiin. KäyttämälläWeakMap-muuttujaa voit varmistaa, että kun DOM-elementti poistetaan sivulta, myös siihen liittyvät metatiedot roskienkerätään, mikä estää muistivuodot.
let elementData = new WeakMap();
function initializeComponent(element) {
let componentData = {
// Komponenttikohtaiset tiedot
isActive: false,
onClick: () => { console.log("Napsautettu!"); }
};
elementData.set(element, componentData);
}
let myElement = document.getElementById("myElement");
initializeComponent(myElement);
// Myöhemmin, kun elementti poistetaan DOM:ista:
// myElement.remove();
// myElement-elementtiin liittyvät komponenttitiedot roskienkerätään lopulta
// kun myElement-elementtiin ei ole muita vahvoja viittauksia.
Tässä esimerkissä elementData tallentaa DOM-elementteihin liittyviä metatietoja. Kun myElement poistetaan DOM:ista, roskienkerääjä voi vapauttaa sen muistin, ja vastaava merkintä elementData-muuttujassa poistetaan automaattisesti.
2. Kalliiden toimintojen tulosten välimuistaminen
Voit käyttää WeakMap-muuttujaa kalliiden toimintojen tulosten välimuistiin syöttöobjektien perusteella. Jos syöttöobjektia ei enää käytetä, välimuistiin tallennettu tulos poistetaan automaattisesti WeakMap-muuttujasta, mikä vapauttaa muistia.
let cache = new WeakMap();
function expensiveOperation(input) {
if (cache.has(input)) {
console.log("Välimuistin osuma!");
return cache.get(input);
}
console.log("Välimuistin ohitus!");
// Suorita kallis toiminto
let result = input.id * 100;
cache.set(input, result);
return result;
}
let obj1 = { id: 5 };
let obj2 = { id: 10 };
console.log(expensiveOperation(obj1)); // Tuloste: Välimuistin ohitus!, 500
console.log(expensiveOperation(obj1)); // Tuloste: Välimuistin osuma!, 500
console.log(expensiveOperation(obj2)); // Tuloste: Välimuistin ohitus!, 1000
obj1 = null;
// Roskienkeräyksen jälkeen merkintä obj1:lle poistetaan välimuistista.
3. Objektien yksityiset tiedot (WeakMap yksityisinä kenttinä)
Ennen yksityisten luokkakenttien käyttöönottoa JavaScriptissä WeakMap oli yleinen tekniikka yksityisten tietojen simulointiin objekteissa. Jokainen objekti yhdistettiin omiin yksityisiin tietoihinsa, jotka oli tallennettu WeakMap-muuttujaan. Koska tietoihin pääsee vain WeakMap-muuttujan ja itse objektin kautta, se on tosiasiallisesti yksityistä.
let _privateData = new WeakMap();
class MyClass {
constructor(secret) {
_privateData.set(this, { secret: secret });
}
getSecret() {
return _privateData.get(this).secret;
}
}
let instance = new MyClass("Oma salainen arvo");
console.log(instance.getSecret()); // Tuloste: Oma salainen arvo
// Suora yritys käyttää _privateData-muuttujaa ei toimi.
// console.log(_privateData.get(instance).secret); // Virhe (jos sinulla jotenkin olisi pääsy _privateData-muuttujaan)
// Vaikka esiintymä roskienkerättäisiin, vastaava merkintä _privateData-muuttujassa poistetaan.
Vaikka yksityiset luokkakentät ovat nykyään ensisijainen lähestymistapa, tämän WeakMap-mallin ymmärtäminen on edelleen arvokasta vanhan koodin ja JavaScriptin historian ymmärtämiseksi.
4. Objektin elinkaaren seuranta
WeakSet-muuttujaa voidaan käyttää objektien elinkaaren seuraamiseen. Voit lisätä objekteja WeakSet-muuttujaan, kun ne luodaan, ja tarkistaa sitten, ovatko ne edelleen olemassa WeakSet-muuttujassa. Kun objekti on roskienkerätty, se poistetaan automaattisesti WeakSet-muuttujasta.
let trackedObjects = new WeakSet();
function trackObject(obj) {
trackedObjects.add(obj);
}
function isObjectTracked(obj) {
return trackedObjects.has(obj);
}
let myObject = { id: 123 };
trackObject(myObject);
console.log(isObjectTracked(myObject)); // Tuloste: true
myObject = null;
// Roskienkeräyksen jälkeen isObjectTracked(myObject) saattaa palauttaa false-arvon.
Globaalit huomioinnit ja parhaat käytännöt
Kun työskentelet WeakMap- ja WeakSet-muuttujien kanssa, ota huomioon nämä globaalit parhaat käytännöt:
- Ymmärrä roskienkeräys: Roskienkeräys ei ole deterministinen. Et voi ennustaa tarkalleen, milloin objekti roskienkerätään. Siksi et voi luottaa siihen, että
WeakMaptaiWeakSetpoistaa merkinnät välittömästi, kun objektiin ei enää viitata. - Vältä liiallista käyttöä: Vaikka
WeakMapjaWeakSetovat hyödyllisiä muistinhallinnassa, älä käytä niitä liikaa. Monissa tapauksissa tavallisetMapjaSetovat täysin riittäviä ja tarjoavat enemmän joustavuutta. KäytäWeakMap- jaWeakSet-muuttujia, kun tarvitset erityisesti heikkoja viittauksia muistivuotojen välttämiseksi. - Käyttötapaukset heikoille viittauksille: Mieti avaimena (
WeakMap) tai arvona (WeakSet) tallentamasi objektin elinkaarta. Jos objekti on sidottu toisen objektin elinkaareen, käytä sittenWeakMap- taiWeakSet-muuttujaa muistivuotojen välttämiseksi. - Testausongelmat: Roskienkeräykseen perustuvan koodin testaaminen voi olla haastavaa. Et voi pakottaa roskienkeräystä JavaScriptissä. Harkitse tekniikoita, kuten suurten objektimäärien luomista ja tuhoamista roskienkeräyksen kannustamiseksi testauksen aikana.
- Polytäyttö: Jos sinun on tuettava vanhempia selaimia, jotka eivät natiivisti tue
WeakMap- jaWeakSet-muuttujia, voit käyttää polytäyttöjä. Polytäytöt eivät kuitenkaan välttämättä pysty toistamaan heikon viittauksen käyttäytymistä täysin, joten testaa perusteellisesti.
Esimerkki: Kansainvälistymisvälimuisti (i18n)
Kuvittele tilanne, jossa rakennat verkkosovellusta, jossa on kansainvälistymistuki (i18n). Haluat ehkä välimuistiin tallentaa käännetyt merkkijonot käyttäjän kieliasetusten perusteella. Voit käyttää WeakMap-muuttujaa välimuistin tallentamiseen, jossa avain on kieliasetusobjekti ja arvo on kyseisen kieliasetuksen käännetyt merkkijonot. Kun kieliasetusta ei enää tarvita (esim. käyttäjä vaihtaa eri kieleen ja vanhaan kieliasetukseen ei enää viitata), kyseisen kieliasetuksen välimuisti roskienkerätään automaattisesti.
let i18nCache = new WeakMap();
function getTranslatedStrings(locale) {
if (i18nCache.has(locale)) {
return i18nCache.get(locale);
}
// Simuloi käännettyjen merkkijonojen hakemista palvelimelta.
let translatedStrings = {
"greeting": (locale.language === "fr") ? "Bonjour" : "Hello",
"farewell": (locale.language === "fr") ? "Au revoir" : "Goodbye"
};
i18nCache.set(locale, translatedStrings);
return translatedStrings;
}
let englishLocale = { language: "en", country: "US" };
let frenchLocale = { language: "fr", country: "FR" };
console.log(getTranslatedStrings(englishLocale).greeting); // Tuloste: Hello
console.log(getTranslatedStrings(frenchLocale).greeting); // Tuloste: Bonjour
englishLocale = null;
// Roskienkeräyksen jälkeen merkintä englishLocale-muuttujalle poistetaan välimuistista.
Tämä lähestymistapa estää i18n-välimuistin kasvamisen loputtomasti ja kuluttamasta liikaa muistia, erityisesti sovelluksissa, jotka tukevat suurta määrää kieliasetuksia.
Johtopäätös
WeakMap ja WeakSet ovat tehokkaita työkaluja muistin hallintaan JavaScript-sovelluksissa. Ymmärtämällä niiden rajoitukset ja käyttötapaukset voit kirjoittaa tehokkaampaa ja vankempaa koodia, joka välttää muistivuodot. Vaikka ne eivät ehkä sovellu jokaiseen tilanteeseen, ne ovat välttämättömiä tilanteissa, joissa sinun on liitettävä tietoja objekteihin estämättä kyseisten objektien roskienkeräystä. Ota nämä kokoelmat käyttöön optimoidaksesi JavaScript-sovelluksesi ja luodaksesi paremman kokemuksen käyttäjillesi riippumatta siitä, missä päin maailmaa he ovat.