మెమరీ-సమర్థవంతమైన అబ్జర్వర్ ప్యాటర్న్ను సృష్టించడానికి జావాస్క్రిప్ట్ యొక్క వీక్రెఫ్ మరియు ఫైనలైజేషన్ రిజిస్ట్రీపై లోతైన విశ్లేషణ. పెద్ద-స్థాయి అప్లికేషన్లలో మెమరీ లీక్లను నివారించడం నేర్చుకోండి.
జావాస్క్రిప్ట్ వీక్రెఫ్ అబ్జర్వర్ ప్యాటర్న్: మెమరీ-అవేర్ ఈవెంట్ సిస్టమ్స్ను నిర్మించడం
ఆధునిక వెబ్ డెవలప్మెంట్ ప్రపంచంలో, డైనమిక్ మరియు రెస్పాన్సివ్ యూజర్ అనుభవాలను సృష్టించడానికి సింగిల్ పేజ్ అప్లికేషన్స్ (SPAs) ప్రామాణికంగా మారాయి. ఈ అప్లికేషన్లు తరచుగా సుదీర్ఘకాలం పాటు నడుస్తాయి, సంక్లిష్టమైన స్టేట్ను నిర్వహిస్తాయి మరియు అసంఖ్యాకమైన యూజర్ ఇంటరాక్షన్లను నిర్వహిస్తాయి. అయితే, ఈ దీర్ఘాయువు ఒక దాగివున్న మూల్యంతో వస్తుంది: మెమరీ లీక్ల ప్రమాదం పెరగడం. ఒక మెమరీ లీక్, అంటే ఒక అప్లికేషన్ తనకు ఇకపై అవసరం లేని మెమరీని పట్టుకొని ఉండటం, కాలక్రమేణా పనితీరును క్షీణింపజేస్తుంది, ఇది మందగమనానికి, బ్రౌజర్ క్రాష్లకు మరియు పేలవమైన యూజర్ అనుభవానికి దారితీస్తుంది. ఈ లీక్లకు అత్యంత సాధారణ కారణాలలో ఒకటి ప్రాథమిక డిజైన్ ప్యాటర్న్లో ఉంది: అబ్జర్వర్ ప్యాటర్న్.
అబ్జర్వర్ ప్యాటర్న్ ఈవెంట్-డ్రైవెన్ ఆర్కిటెక్చర్కు మూలస్తంభం, ఇది ఆబ్జెక్ట్లను (అబ్జర్వర్లు) ఒక కేంద్ర ఆబ్జెక్ట్ (సబ్జెక్ట్) నుండి అప్డేట్లను సబ్స్క్రైబ్ చేయడానికి మరియు స్వీకరించడానికి వీలు కల్పిస్తుంది. ఇది సుందరమైనది, సరళమైనది మరియు చాలా ఉపయోగకరమైనది. కానీ దాని క్లాసిక్ అమలులో ఒక క్లిష్టమైన లోపం ఉంది: సబ్జెక్ట్ దాని అబ్జర్వర్లకు బలమైన రిఫరెన్స్లను నిర్వహిస్తుంది. ఒక అబ్జర్వర్ అప్లికేషన్లోని మిగతా భాగాలకు ఇకపై అవసరం లేనప్పటికీ, డెవలపర్ దానిని సబ్జెక్ట్ నుండి స్పష్టంగా అన్సబ్స్క్రైబ్ చేయడం మర్చిపోతే, అది ఎప్పటికీ గార్బేజ్ కలెక్ట్ చేయబడదు. అది మెమరీలో చిక్కుకుపోయి, మీ అప్లికేషన్ పనితీరును వెంటాడే ఒక దెయ్యంలా మిగిలిపోతుంది.
ఇక్కడే ఆధునిక జావాస్క్రిప్ట్, దాని ECMAScript 2021 (ES12) ఫీచర్లతో, ఒక శక్తివంతమైన పరిష్కారాన్ని అందిస్తుంది. WeakRef మరియు FinalizationRegistryలను ఉపయోగించడం ద్వారా, మనం మెమరీ-అవేర్ అబ్జర్వర్ ప్యాటర్న్ను నిర్మించవచ్చు, ఇది ఈ సాధారణ లీక్లను నివారిస్తూ, స్వయంచాలకంగా తనను తాను శుభ్రం చేసుకుంటుంది. ఈ ఆర్టికల్ ఈ అధునాతన టెక్నిక్పై లోతైన విశ్లేషణ. మనం సమస్యను అన్వేషిస్తాము, సాధనాలను అర్థం చేసుకుంటాము, మొదటి నుండి ఒక బలమైన అమలును నిర్మిస్తాము మరియు మీ గ్లోబల్ అప్లికేషన్లలో ఈ శక్తివంతమైన ప్యాటర్న్ను ఎప్పుడు మరియు ఎక్కడ వర్తింపజేయాలో చర్చిస్తాము.
ప్రధాన సమస్యను అర్థం చేసుకోవడం: క్లాసిక్ అబ్జర్వర్ ప్యాటర్న్ మరియు దాని మెమరీ ఫుట్ప్రింట్
మనం పరిష్కారాన్ని అభినందించే ముందు, సమస్యను పూర్తిగా గ్రహించాలి. అబ్జర్వర్ ప్యాటర్న్, పబ్లిషర్-సబ్స్క్రైబర్ ప్యాటర్న్ అని కూడా పిలువబడుతుంది, కాంపోనెంట్లను డీకపుల్ చేయడానికి రూపొందించబడింది. ఒక సబ్జెక్ట్ (లేదా పబ్లిషర్) దాని డిపెండెంట్ల జాబితాను నిర్వహిస్తుంది, వీటిని అబ్జర్వర్లు (లేదా సబ్స్క్రైబర్లు) అంటారు. సబ్జెక్ట్ యొక్క స్టేట్ మారినప్పుడు, అది స్వయంచాలకంగా దాని అన్ని అబ్జర్వర్లకు తెలియజేస్తుంది, సాధారణంగా వాటిపై update() వంటి ఒక నిర్దిష్ట మెథడ్ను కాల్ చేయడం ద్వారా.
జావాస్క్రిప్ట్లో ఒక సరళమైన, క్లాసిక్ అమలును చూద్దాం.
ఒక సాధారణ సబ్జెక్ట్ అమలు
ఇక్కడ ఒక ప్రాథమిక సబ్జెక్ట్ క్లాస్ ఉంది. దీనికి సబ్స్క్రైబ్, అన్సబ్స్క్రైబ్ మరియు అబ్జర్వర్లకు తెలియజేయడానికి మెథడ్లు ఉన్నాయి.
class ClassicSubject {
constructor() {
this.observers = [];
}
subscribe(observer) {
this.observers.push(observer);
console.log(`${observer.name} has subscribed.`);
}
unsubscribe(observer) {
this.observers = this.observers.filter(obs => obs !== observer);
console.log(`${observer.name} has unsubscribed.`);
}
notify(data) {
console.log('Notifying observers...');
this.observers.forEach(observer => observer.update(data));
}
}
మరియు ఇక్కడ ఒక సాధారణ అబ్జర్వర్ క్లాస్ ఉంది, ఇది సబ్జెక్ట్కు సబ్స్క్రైబ్ చేయగలదు.
class Observer {
constructor(name) {
this.name = name;
}
update(data) {
console.log(`${this.name} received data: ${data}`);
}
}
దాగివున్న ప్రమాదం: మిగిలిపోయిన రిఫరెన్స్లు
ఈ అమలు మనం మన అబ్జర్వర్ల జీవితచక్రాన్ని శ్రద్ధగా నిర్వహించినంత కాలం సంపూర్ణంగా పనిచేస్తుంది. మనం అలా చేయనప్పుడు సమస్య తలెత్తుతుంది. ఒక పెద్ద అప్లికేషన్లో ఒక సాధారణ దృశ్యాన్ని పరిగణించండి: ఒక దీర్ఘకాలిక గ్లోబల్ డేటా స్టోర్ (సబ్జెక్ట్) మరియు కొంత డేటాను ప్రదర్శించే ఒక తాత్కాలిక UI కాంపోనెంట్ (అబ్జర్వర్).
ఈ దృశ్యాన్ని అనుకరిద్దాం:
const dataStore = new ClassicSubject();
function manageUIComponent() {
let chartComponent = new Observer('ChartComponent');
dataStore.subscribe(chartComponent);
// కాంపోనెంట్ తన పనిని చేస్తుంది...
// ఇప్పుడు, యూజర్ వేరే చోటికి నావిగేట్ అయ్యాడు, మరియు కాంపోనెంట్ ఇకపై అవసరం లేదు.
// ఒక డెవలపర్ క్లీనప్ కోడ్ను జోడించడం మర్చిపోవచ్చు:
// dataStore.unsubscribe(chartComponent);
chartComponent = null; // మేము కాంపోనెంట్కు మా రిఫరెన్స్ను విడుదల చేస్తాము.
}
manageUIComponent();
// అప్లికేషన్ జీవితచక్రంలో తరువాత...
dataStore.notify('New data available!');
`manageUIComponent` ఫంక్షన్లో, మనం ఒక `chartComponent`ను సృష్టించి, దానిని మన `dataStore`కు సబ్స్క్రైబ్ చేస్తాము. తరువాత, `chartComponent`ను `null`కు సెట్ చేస్తాము, దీనితో మన పని అయిపోయిందని సూచిస్తాము. జావాస్క్రిప్ట్ గార్బేజ్ కలెక్టర్ (GC) ఈ ఆబ్జెక్ట్కు ఇకపై రిఫరెన్స్లు లేవని చూసి దాని మెమరీని తిరిగి పొందుతుందని మనం ఆశిస్తాము.
కానీ అక్కడ మరొక రిఫరెన్స్ ఉంది! `dataStore.observers` అర్రే ఇప్పటికీ `chartComponent` ఆబ్జెక్ట్కు ఒక ప్రత్యక్ష, బలమైన రిఫరెన్స్ను కలిగి ఉంది. ఈ ఒక్క మిగిలిపోయిన రిఫరెన్స్ కారణంగా, గార్బేజ్ కలెక్టర్ మెమరీని తిరిగి పొందలేదు. `chartComponent` ఆబ్జెక్ట్, మరియు అది కలిగి ఉన్న ఏవైనా వనరులు, `dataStore` జీవితకాలం మొత్తం మెమరీలోనే ఉంటాయి. ఇది పదేపదే జరిగితే—ఉదాహరణకు, యూజర్ ఒక మోడల్ విండోను తెరిచి మూసివేసిన ప్రతిసారీ—అప్లికేషన్ యొక్క మెమరీ వాడకం నిరవధికంగా పెరుగుతుంది. ఇది ఒక క్లాసిక్ మెమరీ లీక్.
ఒక కొత్త ఆశ: వీక్రెఫ్ మరియు ఫైనలైజేషన్ రిజిస్ట్రీ పరిచయం
ECMAScript 2021 ఈ రకమైన మెమరీ నిర్వహణ సవాళ్లను ఎదుర్కోవడానికి ప్రత్యేకంగా రూపొందించిన రెండు కొత్త ఫీచర్లను పరిచయం చేసింది: `WeakRef` మరియు `FinalizationRegistry`. ఇవి అధునాతన సాధనాలు మరియు జాగ్రత్తగా వాడాలి, కానీ మన అబ్జర్వర్ ప్యాటర్న్ సమస్యకు, అవి సరైన పరిష్కారం.
వీక్రెఫ్ అంటే ఏమిటి?
ఒక `WeakRef` ఆబ్జెక్ట్ దాని టార్గెట్ అని పిలువబడే మరొక ఆబ్జెక్ట్కు ఒక బలహీనమైన రిఫరెన్స్ను కలిగి ఉంటుంది. ఒక బలహీనమైన రిఫరెన్స్ మరియు ఒక సాధారణ (బలమైన) రిఫరెన్స్ మధ్య ముఖ్యమైన తేడా ఇది: ఒక బలహీనమైన రిఫరెన్స్ దాని టార్గెట్ ఆబ్జెక్ట్ను గార్బేజ్ కలెక్ట్ చేయకుండా నిరోధించదు.
ఒక ఆబ్జెక్ట్కు ఉన్న ఏకైక రిఫరెన్స్లు బలహీనమైన రిఫరెన్స్లు అయితే, జావాస్క్రిప్ట్ ఇంజిన్ ఆ ఆబ్జెక్ట్ను నాశనం చేయడానికి మరియు దాని మెమరీని తిరిగి పొందడానికి స్వేచ్ఛగా ఉంటుంది. మన అబ్జర్వర్ సమస్యను పరిష్కరించడానికి మనకు కావలసింది ఇదే.
ఒక `WeakRef`ను ఉపయోగించడానికి, మీరు దాని యొక్క ఒక ఇన్స్టాన్స్ను సృష్టిస్తారు, టార్గెట్ ఆబ్జెక్ట్ను కన్స్ట్రక్టర్కు పాస్ చేస్తారు. టార్గెట్ ఆబ్జెక్ట్ను తరువాత యాక్సెస్ చేయడానికి, మీరు `deref()` మెథడ్ను ఉపయోగిస్తారు.
let targetObject = { id: 42 };
const weakRefToObject = new WeakRef(targetObject);
// ఆబ్జెక్ట్ను యాక్సెస్ చేయడానికి:
const retrievedObject = weakRefToObject.deref();
if (retrievedObject) {
console.log(`Object is still alive: ${retrievedObject.id}`); // అవుట్పుట్: Object is still alive: 42
} else {
console.log('Object has been garbage collected.');
}
కీలకమైన భాగం ఏమిటంటే `deref()` `undefined`ను తిరిగి ఇవ్వగలదు. `targetObject`కు బలమైన రిఫరెన్స్లు ఏవీ లేనందున అది గార్బేజ్ కలెక్ట్ చేయబడినప్పుడు ఇది జరుగుతుంది. ఈ ప్రవర్తన మన మెమరీ-అవేర్ అబ్జర్వర్ ప్యాటర్న్కు పునాది.
ఫైనలైజేషన్ రిజిస్ట్రీ అంటే ఏమిటి?
`WeakRef` ఒక ఆబ్జెక్ట్ను కలెక్ట్ చేయడానికి అనుమతించినప్పటికీ, అది ఎప్పుడు కలెక్ట్ చేయబడిందో తెలుసుకోవడానికి మనకు ఒక స్పష్టమైన మార్గాన్ని ఇవ్వదు. మనం క్రమానుగతంగా `deref()`ను తనిఖీ చేసి, మన అబ్జర్వర్ జాబితా నుండి `undefined` ఫలితాలను తీసివేయవచ్చు, కానీ అది సమర్థవంతమైనది కాదు. ఇక్కడే `FinalizationRegistry` ఉపయోగపడుతుంది.
ఒక `FinalizationRegistry` ఒక రిజిస్టర్డ్ ఆబ్జెక్ట్ గార్బేజ్ కలెక్ట్ చేయబడిన తరువాత పిలువబడే ఒక కాల్బ్యాక్ ఫంక్షన్ను రిజిస్టర్ చేయడానికి మిమ్మల్ని అనుమతిస్తుంది. ఇది పోస్ట్-మార్టం క్లీనప్ కోసం ఒక మెకానిజం.
ఇది ఎలా పనిచేస్తుందో ఇక్కడ ఉంది:
- మీరు ఒక క్లీనప్ కాల్బ్యాక్తో ఒక రిజిస్ట్రీని సృష్టిస్తారు.
- మీరు రిజిస్ట్రీతో ఒక ఆబ్జెక్ట్ను `register()` చేస్తారు. మీరు ఒక `heldValue`ను కూడా అందించవచ్చు, ఇది ఆబ్జెక్ట్ కలెక్ట్ చేయబడినప్పుడు మీ కాల్బ్యాక్కు పాస్ చేయబడే డేటా ముక్క. ఈ `heldValue` ఆబ్జెక్ట్కు ప్రత్యక్ష రిఫరెన్స్ అయి ఉండకూడదు, ఎందుకంటే అది ఉద్దేశ్యాన్ని దెబ్బతీస్తుంది!
// 1. క్లీనప్ కాల్బ్యాక్తో రిజిస్ట్రీని సృష్టించండి
const registry = new FinalizationRegistry(heldValue => {
console.log(`An object has been garbage collected. Cleanup token: ${heldValue}`);
});
(function() {
let objectToTrack = { name: 'Temporary Data' };
let cleanupToken = 'temp-data-123';
// 2. ఆబ్జెక్ట్ను రిజిస్టర్ చేసి క్లీనప్ కోసం ఒక టోకెన్ను అందించండి
registry.register(objectToTrack, cleanupToken);
// objectToTrack ఇక్కడ స్కోప్ నుండి బయటకు వెళుతుంది
})();
// భవిష్యత్తులో ఏదో ఒక సమయంలో, GC రన్ అయిన తరువాత, కన్సోల్ లాగ్ చేస్తుంది:
// "An object has been garbage collected. Cleanup token: temp-data-123"
ముఖ్యమైన హెచ్చరికలు మరియు ఉత్తమ పద్ధతులు
మనం అమలులోకి వెళ్లే ముందు, ఈ సాధనాల స్వభావాన్ని అర్థం చేసుకోవడం చాలా ముఖ్యం. గార్బేజ్ కలెక్టర్ యొక్క ప్రవర్తన చాలావరకు ఇంప్లిమెంటేషన్-డిపెండెంట్ మరియు నాన్-డిటర్మినిస్టిక్. దీని అర్థం:
- ఒక ఆబ్జెక్ట్ ఎప్పుడు కలెక్ట్ చేయబడుతుందో మీరు ఊహించలేరు. అది చేరుకోలేనిది అయిన తర్వాత సెకన్లు, నిమిషాలు, లేదా ఇంకా ఎక్కువ సమయం పట్టవచ్చు.
- మీరు `FinalizationRegistry` కాల్బ్యాక్లు సకాలంలో లేదా ఊహించదగిన రీతిలో నడుస్తాయని నమ్మలేరు. అవి క్లీనప్ కోసం, క్లిష్టమైన అప్లికేషన్ లాజిక్ కోసం కాదు.
- `WeakRef` మరియు `FinalizationRegistry`లను అతిగా ఉపయోగించడం కోడ్ను అర్థం చేసుకోవడాన్ని కష్టతరం చేస్తుంది. ఆబ్జెక్ట్ జీవితచక్రాలు స్పష్టంగా మరియు నిర్వహించగలిగేలా ఉంటే, ఎల్లప్పుడూ సరళమైన పరిష్కారాలను (స్పష్టమైన `unsubscribe` కాల్స్ వంటివి) ఇష్టపడండి.
ఈ ఫీచర్లు ఒక ఆబ్జెక్ట్ (అబ్జర్వర్) జీవితచక్రం మరొక ఆబ్జెక్ట్ (సబ్జెక్ట్) నుండి నిజంగా స్వతంత్రంగా మరియు తెలియకుండా ఉన్న పరిస్థితులకు ఉత్తమంగా సరిపోతాయి.
`WeakRefObserver` ప్యాటర్న్ను నిర్మించడం: ఒక దశల వారీ అమలు
ఇప్పుడు, మెమరీ-సురక్షిత `WeakRefSubject` క్లాస్ను నిర్మించడానికి `WeakRef` మరియు `FinalizationRegistry`లను కలుపుదాం.
దశ 1: `WeakRefSubject` క్లాస్ నిర్మాణం
మన కొత్త క్లాస్ ప్రత్యక్ష రిఫరెన్స్లకు బదులుగా అబ్జర్వర్లకు `WeakRef`లను నిల్వ చేస్తుంది. ఇది అబ్జర్వర్ల జాబితాను స్వయంచాలకంగా శుభ్రపరచడానికి ఒక `FinalizationRegistry`ని కూడా కలిగి ఉంటుంది.
class WeakRefSubject {
constructor() {
this.observers = new Set(); // సులభంగా తొలగించడానికి సెట్ను ఉపయోగించడం
// ఫైనలైజర్ కాల్బ్యాక్. ఇది రిజిస్ట్రేషన్ సమయంలో మనం అందించే హెల్డ్ విలువను అందుకుంటుంది.
// మన విషయంలో, హెల్డ్ విలువ WeakRef ఇన్స్టాన్స్ అవుతుంది.
this.cleanupRegistry = new FinalizationRegistry(weakRefObserver => {
console.log('Finalizer: An observer has been garbage collected. Cleaning up...');
this.observers.delete(weakRefObserver);
});
}
}
మనం మన అబ్జర్వర్ల జాబితా కోసం `Array`కు బదులుగా `Set`ను ఉపయోగిస్తాము. ఎందుకంటే `Set` నుండి ఒక ఐటమ్ను తొలగించడం `Array`ను ఫిల్టర్ చేయడం కంటే చాలా సమర్థవంతమైనది (O(1) సగటు సమయ సంక్లిష్టత), ఇది మన క్లీనప్ లాజిక్లో ఉపయోగపడుతుంది.
దశ 2: `subscribe` మెథడ్
`subscribe` మెథడ్ వద్ద మాయాజాలం మొదలవుతుంది. ఒక అబ్జర్వర్ సబ్స్క్రైబ్ చేసినప్పుడు, మనం:
- అబ్జర్వర్కు పాయింట్ చేసే ఒక `WeakRef`ను సృష్టిస్తాము.
- ఈ `WeakRef`ను మన `observers` సెట్కు జోడిస్తాము.
- అసలు అబ్జర్వర్ ఆబ్జెక్ట్ను మన `FinalizationRegistry`తో రిజిస్టర్ చేస్తాము, కొత్తగా సృష్టించబడిన `WeakRef`ను `heldValue`గా ఉపయోగిస్తాము.
// WeakRefSubject క్లాస్ లోపల...
subscribe(observer) {
// ఈ రిఫరెన్స్తో ఒక అబ్జర్వర్ ఇప్పటికే ఉందో లేదో తనిఖీ చేయండి
for (const ref of this.observers) {
if (ref.deref() === observer) {
console.warn('Observer already subscribed.');
return;
}
}
const weakRefObserver = new WeakRef(observer);
this.observers.add(weakRefObserver);
// అసలు అబ్జర్వర్ ఆబ్జెక్ట్ను రిజిస్టర్ చేయండి. అది కలెక్ట్ చేయబడినప్పుడు,
// ఫైనలైజర్ `weakRefObserver`ను ఆర్గ్యుమెంట్గా పిలుస్తుంది.
this.cleanupRegistry.register(observer, weakRefObserver);
console.log('An observer has subscribed.');
}
ఈ సెటప్ ఒక తెలివైన లూప్ను సృష్టిస్తుంది: సబ్జెక్ట్ అబ్జర్వర్కు ఒక బలహీనమైన రిఫరెన్స్ను కలిగి ఉంటుంది. రిజిస్ట్రీ అబ్జర్వర్కు ఒక బలమైన రిఫరెన్స్ను (అంతర్గతంగా) కలిగి ఉంటుంది, అది గార్బేజ్ కలెక్ట్ చేయబడే వరకు. ఒకసారి కలెక్ట్ చేయబడిన తర్వాత, రిజిస్ట్రీ యొక్క కాల్బ్యాక్ బలహీనమైన రిఫరెన్స్ ఇన్స్టాన్స్తో ట్రిగ్గర్ చేయబడుతుంది, దానిని మనం మన `observers` సెట్ను శుభ్రపరచడానికి ఉపయోగించవచ్చు.
దశ 3: `unsubscribe` మెథడ్
స్వయంచాలక క్లీనప్తో కూడా, మనం నిర్దిష్టమైన తొలగింపు అవసరమైన సందర్భాల కోసం మాన్యువల్ `unsubscribe` మెథడ్ను అందించాలి. ఈ మెథడ్ ప్రతి ఒక్కటి డీరిఫరెన్స్ చేసి, మనం తొలగించాలనుకుంటున్న అబ్జర్వర్తో పోల్చడం ద్వారా మన సెట్లో సరైన `WeakRef`ను కనుగొనవలసి ఉంటుంది.
// WeakRefSubject క్లాస్ లోపల...
unsubscribe(observer) {
let refToRemove = null;
for (const weakRef of this.observers) {
if (weakRef.deref() === observer) {
refToRemove = weakRef;
break;
}
}
if (refToRemove) {
this.observers.delete(refToRemove);
// ముఖ్యమైనది: మనం ఫైనలైజర్ నుండి కూడా అన్రిజిస్టర్ చేయాలి
// తరువాత అనవసరంగా కాల్బ్యాక్ రన్ కాకుండా నిరోధించడానికి.
this.cleanupRegistry.unregister(observer);
console.log('An observer has unsubscribed manually.');
}
}
దశ 4: `notify` మెథడ్
`notify` మెథడ్ మన `WeakRef`ల సెట్పై ఇటరేట్ చేస్తుంది. ప్రతి ఒక్కదాని కోసం, అది అసలు అబ్జర్వర్ ఆబ్జెక్ట్ను పొందడానికి `deref()` చేయడానికి ప్రయత్నిస్తుంది. `deref()` విజయవంతమైతే, అబ్జర్వర్ ఇంకా సజీవంగా ఉందని అర్థం, మరియు మనం దాని `update` మెథడ్ను కాల్ చేయవచ్చు. అది `undefined`ను తిరిగి ఇస్తే, అబ్జర్వర్ కలెక్ట్ చేయబడింది, మరియు మనం దానిని పట్టించుకోకుండా ఉండవచ్చు. `FinalizationRegistry` చివరికి దాని `WeakRef`ను సెట్ నుండి తొలగిస్తుంది.
// WeakRefSubject క్లాస్ లోపల...
notify(data) {
console.log('Notifying observers...');
for (const weakRefObserver of this.observers) {
const observer = weakRefObserver.deref();
if (observer) {
// అబ్జర్వర్ ఇంకా సజీవంగా ఉంది
observer.update(data);
} else {
// అబ్జర్వర్ గార్బేజ్ కలెక్ట్ చేయబడింది.
// FinalizationRegistry ఈ weakRef ను సెట్ నుండి తొలగించడాన్ని నిర్వహిస్తుంది.
console.log('Found a dead observer reference during notification.');
}
}
}
అన్నింటినీ కలిపి: ఒక ప్రాక్టికల్ ఉదాహరణ
మన UI కాంపోనెంట్ దృశ్యాన్ని మళ్లీ చూద్దాం, కానీ ఈసారి మన కొత్త `WeakRefSubject`ను ఉపయోగిద్దాం. సరళత కోసం మనం ఇంతకు ముందు ఉపయోగించిన అదే `Observer` క్లాస్ను ఉపయోగిస్తాము.
// అదే సాధారణ అబ్జర్వర్ క్లాస్
class Observer {
constructor(name) {
this.name = name;
}
update(data) {
console.log(`${this.name} received data: ${data}`);
}
}
ఇప్పుడు, ఒక గ్లోబల్ డేటా సర్వీస్ను సృష్టించి, ఒక తాత్కాలిక UI విడ్జెట్ను అనుకరిద్దాం.
const globalDataService = new WeakRefSubject();
function createAndDestroyWidget() {
console.log('--- Creating and subscribing new widget ---');
let chartWidget = new Observer('RealTimeChartWidget');
globalDataService.subscribe(chartWidget);
// విడ్జెట్ ఇప్పుడు యాక్టివ్గా ఉంది మరియు నోటిఫికేషన్లను అందుకుంటుంది
globalDataService.notify({ price: 100 });
console.log('--- Destroying widget (releasing our reference) ---');
// విడ్జెట్తో మన పని అయిపోయింది. మన రిఫరెన్స్ను null కు సెట్ చేస్తాము.
// మనం unsubscribe() ను కాల్ చేయవలసిన అవసరం లేదు.
chartWidget = null;
}
createAndDestroyWidget();
console.log('--- After widget destruction, before garbage collection ---');
globalDataService.notify({ price: 105 });
`createAndDestroyWidget()`ను రన్ చేసిన తర్వాత, `chartWidget` ఆబ్జెక్ట్ ఇప్పుడు మన `globalDataService` లోపల ఉన్న `WeakRef` ద్వారా మాత్రమే రిఫరెన్స్ చేయబడింది. ఇది ఒక బలహీనమైన రిఫరెన్స్ కాబట్టి, ఆ ఆబ్జెక్ట్ ఇప్పుడు గార్బేజ్ కలెక్షన్కు అర్హత పొందింది.
గార్బేజ్ కలెక్టర్ చివరికి రన్ అయినప్పుడు (దీనిని మనం ఊహించలేము), రెండు విషయాలు జరుగుతాయి:
- `chartWidget` ఆబ్జెక్ట్ మెమరీ నుండి తొలగించబడుతుంది.
- మన `FinalizationRegistry` యొక్క కాల్బ్యాక్ ట్రిగ్గర్ చేయబడుతుంది, ఇది ఇప్పుడు-చనిపోయిన `WeakRef`ను `globalDataService.observers` సెట్ నుండి తొలగిస్తుంది.
గార్బేజ్ కలెక్టర్ రన్ అయిన తర్వాత మనం మళ్లీ `notify` కాల్ చేస్తే, `deref()` కాల్ `undefined`ను తిరిగి ఇస్తుంది, చనిపోయిన అబ్జర్వర్ దాటవేయబడుతుంది, మరియు అప్లికేషన్ ఎలాంటి మెమరీ లీక్లు లేకుండా సమర్థవంతంగా నడుస్తూనే ఉంటుంది. మనం అబ్జర్వర్ జీవితచక్రాన్ని సబ్జెక్ట్ నుండి విజయవంతంగా డీకపుల్ చేశాము.
`WeakRefObserver` ప్యాటర్న్ను ఎప్పుడు ఉపయోగించాలి (మరియు ఎప్పుడు నివారించాలి)
ఈ ప్యాటర్న్ శక్తివంతమైనది, కానీ ఇది అన్ని సమస్యలకు పరిష్కారం కాదు. ఇది సంక్లిష్టతను పరిచయం చేస్తుంది మరియు నాన్-డిటర్మినిస్టిక్ ప్రవర్తనపై ఆధారపడుతుంది. ఇది సరైన పని కోసం సరైన సాధనమా అని తెలుసుకోవడం చాలా ముఖ్యం.
ఆదర్శ వినియోగ సందర్భాలు
- దీర్ఘ-కాలిక సబ్జెక్ట్లు మరియు స్వల్ప-కాలిక అబ్జర్వర్లు: ఇది ప్రామాణిక వినియోగ సందర్భం. ఒక గ్లోబల్ సర్వీస్, డేటా స్టోర్, లేదా కాష్ (సబ్జెక్ట్) మొత్తం అప్లికేషన్ జీవితచక్రం పాటు ఉంటుంది, అయితే అనేక UI కాంపోనెంట్లు, తాత్కాలిక వర్కర్లు, లేదా ప్లగిన్లు (అబ్జర్వర్లు) తరచుగా సృష్టించబడి నాశనం చేయబడతాయి.
- కాషింగ్ మెకానిజమ్స్: ఒక కాంప్లెక్స్ ఆబ్జెక్ట్ను కొంత కంప్యూటెడ్ ఫలితానికి మ్యాప్ చేసే ఒక కాష్ను ఊహించుకోండి. మీరు కీ ఆబ్జెక్ట్ కోసం `WeakRef`ను ఉపయోగించవచ్చు. అప్లికేషన్లోని మిగతా భాగాల నుండి అసలు ఆబ్జెక్ట్ గార్బేజ్ కలెక్ట్ చేయబడితే, `FinalizationRegistry` మీ కాష్లోని సంబంధిత ఎంట్రీని స్వయంచాలకంగా శుభ్రపరచగలదు, మెమరీ ఉబ్బరాన్ని నివారిస్తుంది.
- ప్లగిన్ మరియు ఎక్స్టెన్షన్ ఆర్కిటెక్చర్స్: మీరు మూడవ-పక్షం మాడ్యూల్స్ ఈవెంట్లకు సబ్స్క్రైబ్ చేయడానికి అనుమతించే ఒక కోర్ సిస్టమ్ను నిర్మిస్తుంటే, `WeakRefObserver`ను ఉపయోగించడం ఒక రెసిలియన్స్ పొరను జోడిస్తుంది. ఇది అన్సబ్స్క్రైబ్ చేయడం మర్చిపోయిన ఒక పేలవంగా వ్రాసిన ప్లగిన్ మీ కోర్ అప్లికేషన్లో మెమరీ లీక్కు కారణం కాకుండా నివారిస్తుంది.
- డేటాను DOM ఎలిమెంట్స్కు మ్యాప్ చేయడం: ఒక డిక్లరేటివ్ ఫ్రేమ్వర్క్ లేని దృశ్యాలలో, మీరు కొంత డేటాను ఒక DOM ఎలిమెంట్తో అనుబంధించాలనుకోవచ్చు. మీరు దీనిని DOM ఎలిమెంట్ను కీగా ఉన్న ఒక మ్యాప్లో నిల్వ చేస్తే, ఎలిమెంట్ DOM నుండి తీసివేయబడినప్పటికీ ఇంకా మీ మ్యాప్లో ఉంటే మీరు ఒక మెమరీ లీక్ను సృష్టించవచ్చు. ఇక్కడ `WeakMap` ఒక మంచి ఎంపిక, కానీ సూత్రం అదే: డేటా జీవితచక్రం ఎలిమెంట్ జీవితచక్రానికి ముడిపడి ఉండాలి, దాని వ్యతిరేకం కాదు.
క్లాసిక్ అబ్జర్వర్తో ఎప్పుడు ఉండాలి
- గట్టిగా ముడిపడిన జీవితచక్రాలు: సబ్జెక్ట్ మరియు దాని అబ్జర్వర్లు ఎల్లప్పుడూ కలిసి లేదా ఒకే స్కోప్లో సృష్టించబడి మరియు నాశనం చేయబడితే, `WeakRef` యొక్క ఓవర్హెడ్ మరియు సంక్లిష్టత అనవసరం. ఒక సరళమైన, స్పష్టమైన `unsubscribe()` కాల్ మరింత చదవగలిగేదిగా మరియు ఊహించదగినదిగా ఉంటుంది.
- పనితీరు-క్లిష్టమైన హాట్ పాత్స్: `deref()` మెథడ్కు చిన్నదైనా కాని సున్నా కాని పనితీరు ఖర్చు ఉంటుంది. మీరు సెకనుకు వందల సార్లు వేలాది అబ్జర్వర్లకు తెలియజేస్తుంటే (ఉదాహరణకు, ఒక గేమ్ లూప్లో లేదా హై-ఫ్రీక్వెన్సీ డేటా విజువలైజేషన్లో), ప్రత్యక్ష రిఫరెన్స్లతో క్లాసిక్ అమలు వేగంగా ఉంటుంది.
- సరళమైన అప్లికేషన్లు మరియు స్క్రిప్ట్లు: చిన్న అప్లికేషన్లు లేదా అప్లికేషన్ జీవితకాలం తక్కువగా ఉన్న మరియు మెమరీ నిర్వహణ ఒక ముఖ్యమైన ఆందోళన కాని స్క్రిప్ట్ల కోసం, క్లాసిక్ ప్యాటర్న్ అమలు చేయడానికి మరియు అర్థం చేసుకోవడానికి సరళమైనది. అవసరం లేని చోట సంక్లిష్టతను జోడించవద్దు.
- డిటర్మినిస్టిక్ క్లీనప్ అవసరమైనప్పుడు: ఒక అబ్జర్వర్ వేరు చేయబడిన ఖచ్చితమైన క్షణంలో మీరు ఒక చర్యను చేయవలసి వస్తే (ఉదాహరణకు, ఒక కౌంటర్ను అప్డేట్ చేయడం, ఒక నిర్దిష్ట హార్డ్వేర్ వనరును విడుదల చేయడం), మీరు తప్పనిసరిగా మాన్యువల్ `unsubscribe()` మెథడ్ను ఉపయోగించాలి. `FinalizationRegistry` యొక్క నాన్-డిటర్మినిస్టిక్ స్వభావం ఊహించదగిన విధంగా అమలు చేయవలసిన లాజిక్కు దానిని అనర్హమైనదిగా చేస్తుంది.
సాఫ్ట్వేర్ ఆర్కిటెక్చర్కు విస్తృత निहितार्थాలు
జావాస్క్రిప్ట్ వంటి ఉన్నత-స్థాయి భాషలోకి బలహీనమైన రిఫరెన్స్ల పరిచయం ప్లాట్ఫారమ్ యొక్క పరిపక్వతను సూచిస్తుంది. ఇది డెవలపర్లు మరింత అధునాతనమైన మరియు స్థితిస్థాపకమైన సిస్టమ్లను నిర్మించడానికి అనుమతిస్తుంది, ముఖ్యంగా దీర్ఘ-కాలం నడిచే అప్లికేషన్ల కోసం. ఈ ప్యాటర్న్ ఆర్కిటెక్చరల్ ఆలోచనలో ఒక మార్పును ప్రోత్సహిస్తుంది:
- నిజమైన డీకప్లింగ్: ఇది కేవలం ఇంటర్ఫేస్ దాటి ఒక స్థాయి డీకప్లింగ్ను అనుమతిస్తుంది. మనం ఇప్పుడు కాంపోనెంట్ల జీవితచక్రాలను కూడా డీకపుల్ చేయవచ్చు. సబ్జెక్ట్ దాని అబ్జర్వర్లు ఎప్పుడు సృష్టించబడతాయో లేదా నాశనం చేయబడతాయో గురించి ఏమీ తెలుసుకోవలసిన అవసరం లేదు.
- డిజైన్ ద్వారా స్థితిస్థాపకత: ఇది ప్రోగ్రామర్ పొరపాట్లకు మరింత స్థితిస్థాపకంగా ఉండే సిస్టమ్లను నిర్మించడంలో సహాయపడుతుంది. ఒక మర్చిపోయిన `unsubscribe()` కాల్ ట్రాక్ చేయడం కష్టంగా ఉండే ఒక సాధారణ బగ్. ఈ ప్యాటర్న్ ఆ మొత్తం తరగతి లోపాలను తగ్గిస్తుంది.
- ఫ్రేమ్వర్క్ మరియు లైబ్రరీ రచయితలను ప్రారంభించడం: ఇతర డెవలపర్ల కోసం ఫ్రేమ్వర్క్లు, లైబ్రరీలు, లేదా ప్లాట్ఫారమ్లను నిర్మించే వారికి, ఈ సాధనాలు అమూల్యమైనవి. అవి లైబ్రరీ వినియోగదారుల ద్వారా దుర్వినియోగానికి తక్కువ గురయ్యే బలమైన APIలను సృష్టించడానికి అనుమతిస్తాయి, ఇది మొత్తం మీద మరింత స్థిరమైన అప్లికేషన్లకు దారితీస్తుంది.
ముగింపు: ఆధునిక జావాస్క్రిప్ట్ డెవలపర్కు ఒక శక్తివంతమైన సాధనం
క్లాసిక్ అబ్జర్వర్ ప్యాటర్న్ సాఫ్ట్వేర్ డిజైన్ యొక్క ఒక ప్రాథమిక నిర్మాణ భాగం, కానీ దాని బలమైన రిఫరెన్స్లపై ఆధారపడటం జావాస్క్రిప్ట్ అప్లికేషన్లలో సూక్ష్మమైన మరియు నిరాశపరిచే మెమరీ లీక్లకు చాలాకాలంగా ఒక మూలంగా ఉంది. ES2021లో `WeakRef` మరియు `FinalizationRegistry` రాకతో, ఈ పరిమితిని అధిగమించడానికి మనకు ఇప్పుడు సాధనాలు ఉన్నాయి.
మనం మిగిలిపోయిన రిఫరెన్స్ల యొక్క ప్రాథమిక సమస్యను అర్థం చేసుకోవడం నుండి మొదటి నుండి ఒక సంపూర్ణ, మెమరీ-అవేర్ `WeakRefSubject`ను నిర్మించడం వరకు ప్రయాణించాము. `WeakRef` 'గమనించబడుతున్నప్పుడు' కూడా ఆబ్జెక్ట్లను గార్బేజ్ కలెక్ట్ చేయడానికి ఎలా అనుమతిస్తుందో, మరియు `FinalizationRegistry` మన అబ్జర్వర్ జాబితాను స్వచ్ఛంగా ఉంచడానికి స్వయంచాలక క్లీనప్ మెకానిజంను ఎలా అందిస్తుందో మనం చూశాము.
అయితే, గొప్ప శక్తితో గొప్ప బాధ్యత వస్తుంది. ఇవి అధునాతన ఫీచర్లు, వాటి నాన్-డిటర్మినిస్టిక్ స్వభావానికి జాగ్రత్తగా పరిశీలన అవసరం. అవి మంచి అప్లికేషన్ డిజైన్ మరియు శ్రద్ధగల జీవితచక్ర నిర్వహణకు ప్రత్యామ్నాయం కాదు. కానీ సరైన సమస్యలకు వర్తింపజేసినప్పుడు—దీర్ఘ-కాలిక సర్వీసులు మరియు అశాశ్వతమైన కాంపోనెంట్ల మధ్య కమ్యూనికేషన్ను నిర్వహించడం వంటివి—వీక్రెఫ్ అబ్జర్వర్ ప్యాటర్న్ ఒక అసాధారణమైన శక్తివంతమైన టెక్నిక్. దీనిని నైపుణ్యం సాధించడం ద్వారా, మీరు మరింత బలమైన, సమర్థవంతమైన, మరియు స్కేలబుల్ జావాస్క్రిప్ట్ అప్లికేషన్లను వ్రాయవచ్చు, ఆధునిక, డైనమిక్ వెబ్ యొక్క డిమాండ్లను తీర్చడానికి సిద్ధంగా ఉంటాయి.