अलग की गई विंडो की वजह से होने वाली मेमोरी लीक को ढूंढना और ठीक करना.
JavaScript में मेमोरी लीक क्या है?
मेमोरी लीक का मतलब है कि समय के साथ किसी ऐप्लिकेशन में इस्तेमाल की जाने वाली मेमोरी में अनजाने में बढ़ोतरी हो जाती है. JavaScript में, मेमोरी लीक तब होती है, जब ऑब्जेक्ट की ज़रूरत नहीं होती, लेकिन फ़ंक्शन या अन्य ऑब्जेक्ट अब भी उनका रेफ़रंस देते हैं. इन रेफ़रंस की मदद से, गै़रबेज कलेक्टर को ज़रूरत के मुताबिक ऑब्जेक्ट वापस पाने से रोका जाता है.
गै़रबेज कलेक्टर का काम, उन ऑब्जेक्ट की पहचान करना और उन्हें वापस पाना है जो अब ऐप्लिकेशन से ऐक्सेस नहीं किए जा सकते. यह तब भी काम करता है, जब ऑब्जेक्ट खुद का रेफ़रंस देते हैं या एक-दूसरे का रेफ़रंस देते हैं. जब कोई ऐप्लिकेशन ऑब्जेक्ट के ग्रुप को ऐक्सेस करने के लिए कोई रेफ़रंस नहीं छोड़ता है, तो उसे गै़रबेज कलेक्शन किया जा सकता है.
let A = {};
console.log(A); // local variable reference
let B = {A}; // B.A is a second reference to A
A = null; // unset local variable reference
console.log(B.A); // A can still be referenced by B
B.A = null; // unset B's reference to A
// No references to A are left. It can be garbage collected.
मेमोरी लीक की एक खास क्लास तब होती है, जब कोई ऐप्लिकेशन ऐसे ऑब्जेक्ट का रेफ़रंस देता है जिनका अपना लाइफ़साइकल होता है. जैसे, डीओएम एलिमेंट या पॉप-अप विंडो. ऐसा हो सकता है कि इस तरह के ऑब्जेक्ट, ऐप्लिकेशन के इस्तेमाल के बिना ही इस्तेमाल न किए जाएं. इसका मतलब है कि ऐप्लिकेशन कोड में, किसी ऐसे ऑब्जेक्ट के सिर्फ़ रेफ़रंस बचे हों जिसे ग़ैर-ज़रूरी डेटा के तौर पर हटाया जा सकता है.
अलग की गई विंडो क्या होती है?
नीचे दिए गए उदाहरण में, स्लाइड शो व्यूअर ऐप्लिकेशन में प्रज़ेंटर के नोट वाले पॉप-अप को खोलने और बंद करने के लिए बटन शामिल हैं. मान लें कि कोई उपयोगकर्ता नोट दिखाएं पर क्लिक करता है. इसके बाद, नोट छिपाएं बटन पर क्लिक करने के बजाय, सीधे पॉप-अप विंडो को बंद कर देता है. ऐसे में, notesWindow
वैरिएबल में अब भी पॉप-अप का रेफ़रंस मौजूद होता है. इस रेफ़रंस को ऐक्सेस किया जा सकता है, भले ही पॉप-अप का इस्तेमाल अब न किया जा रहा हो.
<button id="show">Show Notes</button>
<button id="hide">Hide Notes</button>
<script type="module">
let notesWindow;
document.getElementById('show').onclick = () => {
notesWindow = window.open('/presenter-notes.html');
};
document.getElementById('hide').onclick = () => {
if (notesWindow) notesWindow.close();
};
</script>
यह अलग की गई विंडो का उदाहरण है. पॉप-अप विंडो बंद हो गई थी, लेकिन हमारे कोड में इसका रेफ़रंस है. इस वजह से, ब्राउज़र उसे मिटा नहीं पाता और उस मेमोरी को वापस नहीं पाता.
जब कोई पेज नई ब्राउज़र विंडो या टैब बनाने के लिए window.open()
को कॉल करता है, तो एक Window
ऑब्जेक्ट दिखाया जाता है, जो विंडो या टैब को दिखाता है. ऐसी विंडो बंद होने या उपयोगकर्ता के किसी दूसरी विंडो पर जाने के बाद भी, window.open()
से मिले Window
ऑब्जेक्ट का इस्तेमाल, उसके बारे में जानकारी ऐक्सेस करने के लिए किया जा सकता है. यह अलग की गई विंडो का एक टाइप है: JavaScript कोड अब भी बंद Window
ऑब्जेक्ट की प्रॉपर्टी ऐक्सेस कर सकता है. इसलिए, इसे मेमोरी में रखना ज़रूरी है. अगर विंडो में बहुत ज़्यादा JavaScript ऑब्जेक्ट या iframe शामिल हैं, तो उस मेमोरी को तब तक वापस नहीं पाया जा सकता, जब तक विंडो की प्रॉपर्टी में कोई JavaScript रेफ़रंस नहीं रह जाता.
<iframe>
एलिमेंट का इस्तेमाल करने पर भी यही समस्या आ सकती है. iframe, नेस्ट की गई उन विंडो की तरह काम करते हैं जिनमें दस्तावेज़ होते हैं. साथ ही, उनकी contentWindow
प्रॉपर्टी, Window
ऑब्जेक्ट को ऐक्सेस करने की सुविधा देती है. यह वैल्यू, window.open()
से मिलने वाली वैल्यू की तरह ही होती है. JavaScript कोड, iframe के contentWindow
या contentDocument
का रेफ़रंस रख सकता है. भले ही, iframe को DOM से हटा दिया गया हो या उसका यूआरएल बदल गया हो. इससे दस्तावेज़ को ग़ैर-ज़रूरी डेटा के तौर पर हटाने से रोका जा सकता है, क्योंकि उसकी प्रॉपर्टी अब भी ऐक्सेस की जा सकती हैं.
अगर किसी विंडो या iframe में document
का रेफ़रंस, JavaScript से बनाए रखा जाता है, तो उस दस्तावेज़ को मेमोरी में रखा जाएगा. भले ही, उस विंडो या iframe में कोई नया यूआरएल खोला गया हो. यह समस्या तब खास तौर पर परेशानी पैदा कर सकती है, जब उस रेफ़रंस को होल्ड करने वाले JavaScript को पता न चले कि विंडो/फ़्रेम किसी नए यूआरएल पर नेविगेट हो गया है. ऐसा इसलिए, क्योंकि उसे यह पता नहीं होता कि वह दस्तावेज़ को मेमोरी में सेव करने वाला आखिरी रेफ़रंस कब बन गया.
अलग की गई विंडो से मेमोरी लीक कैसे होती है
प्राइमरी पेज के डोमेन पर विंडो और iframe के साथ काम करते समय, आम तौर पर इवेंट को सुनने या दस्तावेज़ की सीमाओं के पार प्रॉपर्टी को ऐक्सेस करने की ज़रूरत होती है. उदाहरण के लिए, इस गाइड की शुरुआत में दिए गए प्रज़ेंटेशन व्यूअर के उदाहरण पर फिर से गौर करें. प्रज़ेंटर के नोट दिखाने के लिए, दर्शक दूसरी विंडो खोलते हैं. स्पीकर नोट विंडो, अगली स्लाइड पर जाने के लिए, click
इवेंट को अपनी क्यू के तौर पर सुनती है. अगर उपयोगकर्ता इस नोट वाली विंडो को बंद कर देता है, तो मूल पैरंट विंडो में चल रहे JavaScript के पास अब भी स्पीकर नोट के दस्तावेज़ का पूरा ऐक्सेस होता है:
<button id="notes">Show Presenter Notes</button>
<script type="module">
let notesWindow;
function showNotes() {
notesWindow = window.open('/presenter-notes.html');
notesWindow.document.addEventListener('click', nextSlide);
}
document.getElementById('notes').onclick = showNotes;
let slide = 1;
function nextSlide() {
slide += 1;
notesWindow.document.title = `Slide ${slide}`;
}
document.body.onclick = nextSlide;
</script>
मान लें कि हमने ऊपर showNotes()
से बनाई गई ब्राउज़र विंडो बंद कर दी है. विंडो बंद होने का पता लगाने के लिए, कोई इवेंट हैंडलर नहीं है. इसलिए, हमारे कोड को यह जानकारी नहीं मिल रही है कि उसे दस्तावेज़ के किसी भी रेफ़रंस को हटा देना चाहिए. nextSlide()
फ़ंक्शन अब भी "लाइव" है, क्योंकि इसे हमारे मुख्य पेज में क्लिक हैंडलर के तौर पर बाउंड किया गया है. साथ ही, nextSlide
में notesWindow
का रेफ़रंस होने का मतलब है कि विंडो का अब भी रेफ़रंस दिया गया है और इसे ग़ैर-ज़रूरी डेटा के तौर पर हटाया नहीं जा सकता.
ऐसे कई और मामले भी हैं जिनमें रेफ़रंस गलती से सेव हो जाते हैं. इनकी वजह से, अलग की गई विंडो, गै़रबेज कलेक्शन की ज़रूरी शर्तें पूरी नहीं कर पाती हैं:
इवेंट हैंडलर, फ़्रेम के अपने टारगेट यूआरएल पर नेविगेट करने से पहले, iframe के शुरुआती दस्तावेज़ पर रजिस्टर किए जा सकते हैं. इस वजह से, अन्य रेफ़रंस हटाने के बाद भी, दस्तावेज़ और iframe के रेफ़रंस बने रहते हैं.
किसी विंडो या iframe में लोड किया गया ज़्यादा मेमोरी वाला दस्तावेज़, नए यूआरएल पर जाने के बाद भी गलती से मेमोरी में बना रह सकता है. ऐसा अक्सर तब होता है, जब पैरंट पेज पर दस्तावेज़ के रेफ़रंस सेव रहते हैं, ताकि दर्शक को हटाया जा सके.
किसी JavaScript ऑब्जेक्ट को किसी दूसरी विंडो या iframe में पास करते समय, ऑब्जेक्ट की प्रोटोटाइप चेन में उस एनवायरमेंट के रेफ़रंस शामिल होते हैं जिसमें उसे बनाया गया था. इसमें उसे बनाने वाली विंडो भी शामिल होती है. इसका मतलब है कि दूसरी विंडो के ऑब्जेक्ट के रेफ़रंस से बचना उतना ही ज़रूरी है जितना कि खुद विंडो के रेफ़रंस से बचना.
index.html:
<script> let currentFiles; function load(files) { // this retains the popup: currentFiles = files; } window.open('upload.html'); </script>
upload.html:
<input type="file" id="file" /> <script> file.onchange = () => { parent.load(file.files); }; </script>
अलग की गई विंडो की वजह से मेमोरी लीक का पता लगाना
मेमोरी लीक का पता लगाना मुश्किल हो सकता है. आम तौर पर, इन समस्याओं को अलग-अलग तरीके से दोहराना मुश्किल होता है. खास तौर पर, जब कई दस्तावेज़ या विंडो शामिल हों. बात को और मुश्किल बनाने के लिए, लीक होने वाले संभावित रेफ़रंस की जांच करने पर, अतिरिक्त रेफ़रंस बन सकते हैं. ये रेफ़रंस, जांच किए गए ऑब्जेक्ट को ग़ैर-ज़रूरी डेटा के तौर पर हटाने से रोकते हैं. इसलिए, ऐसे टूल का इस्तेमाल करना शुरू करें जो इस तरह की संभावना को रोकते हैं.
हीप का स्नैपशॉट लें. इससे, मेमोरी से जुड़ी समस्याओं को डीबग करने में मदद मिलती है. इससे, किसी ऐप्लिकेशन के इस्तेमाल में मौजूद मेमोरी का एक पॉइंट-इन-टाइम व्यू मिलता है. इसमें वे सभी ऑब्जेक्ट शामिल होते हैं जिन्हें बनाया गया है, लेकिन अब तक गै़रबेज इकट्ठा नहीं किया गया है. हीप स्नैपशॉट में ऑब्जेक्ट के बारे में काम की जानकारी होती है. इसमें उनके साइज़ के साथ-साथ, उन वैरिएबल और क्लोज़र की सूची भी शामिल होती है जिनमें उनका रेफ़रंस दिया गया है.
ढेर का स्नैपशॉट रिकॉर्ड करने के लिए, Chrome DevTools में मेमोरी टैब पर जाएं. इसके बाद, उपलब्ध प्रोफ़ाइलिंग टाइप की सूची में से ढेर का स्नैपशॉट चुनें. रिकॉर्डिंग पूरी हो जाने के बाद, खास जानकारी व्यू में, मेमोरी में मौजूद मौजूदा ऑब्जेक्ट दिखते हैं. इन्हें कन्स्ट्रक्टर के हिसाब से ग्रुप किया जाता है.
हीप डंप का विश्लेषण करना मुश्किल हो सकता है. साथ ही, डीबग करने के दौरान सही जानकारी ढूंढना भी मुश्किल हो सकता है. इस समस्या को हल करने के लिए, Chromium के इंजीनियर yossik@ और peledni@ ने एक स्टैंडअलोन हीप क्लीनर टूल बनाया है. इसकी मदद से, किसी खास नोड को हाइलाइट किया जा सकता है, जैसे कि अलग की गई विंडो. किसी ट्रेस पर 'हीप क्लीनर' चलाने से, निजी डेटा के रखरखाव के ग्राफ़ से अन्य ग़ैर-ज़रूरी जानकारी हट जाती है. इससे ट्रेस साफ़ हो जाता है और उसे पढ़ना आसान हो जाता है.
प्रोग्राम के हिसाब से मेमोरी को मेज़र करना
हीप स्नैपशॉट से ज़्यादा जानकारी मिलती है. साथ ही, यह पता लगाने में मदद मिलती है कि लीक कहां से हो रही है. हालांकि, हीप स्नैपशॉट लेने की प्रोसेस मैन्युअल होती है. मेमोरी लीक की जांच करने का एक और तरीका है, performance.memory
एपीआई से फ़िलहाल इस्तेमाल किए जा रहे JavaScript ढेर का साइज़ पाना:
performance.memory
API सिर्फ़ JavaScript ढेर के साइज़ की जानकारी देता है. इसका मतलब है कि इसमें पॉप-अप के दस्तावेज़ और संसाधनों के इस्तेमाल की मेमोरी शामिल नहीं होती. पूरी जानकारी पाने के लिए, हमें नए performance.measureUserAgentSpecificMemory()
एपीआई का इस्तेमाल करना होगा. फ़िलहाल, इस एपीआई को Chrome में आज़माया जा रहा है.
विंडो के अलग होने से होने वाली समस्याओं से बचने के तरीके
अलग की गई विंडो से मेमोरी लीक होने के दो सबसे सामान्य मामले ये हैं: जब पैरंट दस्तावेज़, बंद किए गए पॉप-अप या हटाए गए iframe के रेफ़रंस को सेव रखता है और जब किसी विंडो या iframe पर अचानक नेविगेट करने की वजह से, इवेंट हैंडलर कभी भी अनरजिस्टर नहीं होते.
उदाहरण: पॉप-अप बंद करना
नीचे दिए गए उदाहरण में, पॉप-अप विंडो को खोलने और बंद करने के लिए दो बटन का इस्तेमाल किया गया है. पॉप-अप बंद करें बटन के काम करने के लिए, खुली हुई पॉप-अप विंडो का रेफ़रंस, वैरिएबल में सेव किया जाता है:
<button id="open">Open Popup</button>
<button id="close">Close Popup</button>
<script>
let popup;
open.onclick = () => {
popup = window.open('/login.html');
};
close.onclick = () => {
popup.close();
};
</script>
पहली नज़र में, ऐसा लगता है कि ऊपर दिया गया कोड सामान्य गड़बड़ियों से बचता है: पॉप-अप के दस्तावेज़ का कोई रेफ़रंस नहीं रखा जाता और पॉप-अप विंडो पर कोई इवेंट हैंडलर रजिस्टर नहीं किया जाता. हालांकि, पॉप-अप खोलें बटन पर क्लिक करने के बाद, popup
वैरिएबल अब खुली हुई विंडो का रेफ़रंस देता है. साथ ही, उस वैरिएबल को पॉप-अप बंद करें बटन के क्लिक हैंडलर के दायरे से ऐक्सेस किया जा सकता है. जब तक popup
को फिर से असाइन नहीं किया जाता या क्लिक हैंडलर को हटाया नहीं जाता, तब तक उस हैंडलर के popup
के साथ शामिल रेफ़रंस का मतलब है कि उसे ग़ैर-ज़रूरी डेटा के तौर पर हटाया नहीं जा सकता.
समाधान: रेफ़रंस हटाना
किसी दूसरी विंडो या उसके दस्तावेज़ का रेफ़रंस देने वाले वैरिएबल, मेमोरी में सेव रहते हैं. JavaScript में ऑब्जेक्ट हमेशा रेफ़रंस होते हैं. इसलिए, वेरिएबल को नई वैल्यू असाइन करने पर, उनका मूल ऑब्जेक्ट का रेफ़रंस हट जाता है. किसी ऑब्जेक्ट के रेफ़रंस को "अनसेट" करने के लिए, हम उन वैरिएबल को null
वैल्यू पर फिर से असाइन कर सकते हैं.
इसे पिछले पॉप-अप उदाहरण पर लागू करने के लिए, हम बंद करने वाले बटन के हैंडलर में बदलाव कर सकते हैं, ताकि पॉप-अप विंडो का रेफ़रंस "अनसेट" हो जाए:
let popup;
open.onclick = () => {
popup = window.open('/login.html');
};
close.onclick = () => {
popup.close();
popup = null;
};
इससे मदद मिलती है, लेकिन open()
का इस्तेमाल करके बनाई गई विंडो से जुड़ी एक और समस्या का पता चलता है: अगर उपयोगकर्ता, हमारे कस्टम 'बंद करें' बटन पर क्लिक करने के बजाय, विंडो को बंद कर देता है, तो क्या होगा? इसके अलावा, अगर उपयोगकर्ता हमारी खोली गई विंडो में दूसरी वेबसाइटें ब्राउज़ करना शुरू कर दे, तो क्या होगा? पहले हमें लगता था कि popup
रेफ़रंस को बंद करने के बटन पर क्लिक करने पर, उसे अनसेट करना काफ़ी है. हालांकि, जब उपयोगकर्ता विंडो को बंद करने के लिए उस बटन का इस्तेमाल नहीं करते, तब भी मेमोरी लीक होती है. इस समस्या को हल करने के लिए, इन मामलों का पता लगाना ज़रूरी है, ताकि जब भी ये मामले सामने आएं, तो उनसे जुड़े रेफ़रंस को अनसेट किया जा सके.
समाधान: निगरानी करना और डिस्पोज करना
कई मामलों में, विंडो खोलने या फ़्रेम बनाने के लिए ज़िम्मेदार JavaScript के पास, उनके लाइफ़साइकल पर खास कंट्रोल नहीं होता. उपयोगकर्ता पॉप-अप बंद कर सकता है या किसी नए दस्तावेज़ पर नेविगेट करने पर, हो सकता है कि पहले विंडो या फ़्रेम में मौजूद दस्तावेज़ अलग हो जाए. दोनों मामलों में, ब्राउज़र pagehide
इवेंट को ट्रिगर करता है, ताकि यह सिग्नल दिया जा सके कि दस्तावेज़ को अनलोड किया जा रहा है.
pagehide
इवेंट का इस्तेमाल, बंद विंडो और मौजूदा दस्तावेज़ से नेविगेट करने का पता लगाने के लिए किया जा सकता है. हालांकि, एक अहम बात ध्यान रखें: सभी नई विंडो और iframe में एक खाली दस्तावेज़ होता है. इसके बाद, अगर कोई यूआरएल दिया गया है, तो उस पर असींक्रोनस तरीके से नेविगेट किया जाता है. इस वजह से, विंडो या फ़्रेम बनाने के कुछ समय बाद, टारगेट दस्तावेज़ लोड होने से ठीक पहले, एक शुरुआती pagehide
इवेंट ट्रिगर होता है. टारगेट दस्तावेज़ के अनलोड होने पर, हमारे रेफ़रंस क्लीनअप कोड को चलाना ज़रूरी है. इसलिए, हमें इस पहले pagehide
इवेंट को अनदेखा करना होगा. ऐसा करने के लिए कई तकनीकें हैं. इनमें से सबसे आसान तरीका यह है कि शुरुआती दस्तावेज़ के about:blank
यूआरएल से शुरू होने वाले पेज छिपाने वाले इवेंट को अनदेखा करें. पॉप-अप के उदाहरण में यह ऐसा दिखेगा:
let popup;
open.onclick = () => {
popup = window.open('/login.html');
// listen for the popup being closed/exited:
popup.addEventListener('pagehide', () => {
// ignore initial event fired on "about:blank":
if (!popup.location.host) return;
// remove our reference to the popup window:
popup = null;
});
};
ध्यान रखें कि यह तरीका सिर्फ़ उन विंडो और फ़्रेम के लिए काम करता है जिनका ऑरिजिन, उस पैरंट पेज जैसा ही हो जहां हमारा कोड चल रहा है. किसी दूसरे ऑरिजिन से कॉन्टेंट लोड करते समय, सुरक्षा से जुड़ी वजहों से location.host
और pagehide
, दोनों इवेंट उपलब्ध नहीं होते. आम तौर पर, अन्य ऑरिजिन के रेफ़रंस से बचना सबसे अच्छा होता है. हालांकि, कुछ मामलों में ऐसा करना ज़रूरी हो सकता है. ऐसे में, window.closed
या frame.isConnected
प्रॉपर्टी को मॉनिटर किया जा सकता है. जब ये प्रॉपर्टी, बंद की गई विंडो या हटाए गए iframe को दिखाने के लिए बदल जाती हैं, तो इनके किसी भी रेफ़रंस को अनसेट करना अच्छा होता है.
let popup = window.open('https://example.com');
let timer = setInterval(() => {
if (popup.closed) {
popup = null;
clearInterval(timer);
}
}, 1000);
समाधान: WeakRef का इस्तेमाल करना
JavaScript में हाल ही में, ऑब्जेक्ट का रेफ़रंस देने का एक नया तरीका जोड़ा गया है. इसकी मदद से, गै़रबेज कलेक्शन की प्रोसेस शुरू की जा सकती है. इसे WeakRef
कहा जाता है. किसी ऑब्जेक्ट के लिए बनाया गया WeakRef
, सीधा रेफ़रंस नहीं होता. यह एक अलग ऑब्जेक्ट होता है, जो एक खास .deref()
तरीका उपलब्ध कराता है. यह तरीका, तब तक ऑब्जेक्ट का रेफ़रंस दिखाता है, जब तक उसे गै़रबेज-इकट्ठा करने की प्रोसेस के ज़रिए हटाया नहीं जाता. WeakRef
की मदद से, किसी विंडो या दस्तावेज़ की मौजूदा वैल्यू को ऐक्सेस किया जा सकता है. साथ ही, उसमें ग़ैर-ज़रूरी डेटा इकट्ठा करने की अनुमति भी दी जा सकती है. pagehide
जैसे इवेंट या window.closed
जैसी प्रॉपर्टी के जवाब में, मैन्युअल तरीके से सेट किए गए विंडो के रेफ़रंस को बनाए रखने के बजाय, ज़रूरत के हिसाब से विंडो का ऐक्सेस लिया जाता है. विंडो बंद होने पर, उसे ग़ैर-ज़रूरी डेटा के तौर पर हटाया जा सकता है. इस वजह से, .deref()
तरीका undefined
दिखाना शुरू कर देता है.
<button id="open">Open Popup</button>
<button id="close">Close Popup</button>
<script>
let popup;
open.onclick = () => {
popup = new WeakRef(window.open('/login.html'));
};
close.onclick = () => {
const win = popup.deref();
if (win) win.close();
};
</script>
विंडो या दस्तावेज़ों को ऐक्सेस करने के लिए WeakRef
का इस्तेमाल करते समय, एक दिलचस्प जानकारी पर ध्यान देना ज़रूरी है. आम तौर पर, विंडो बंद होने या iframe हटने के बाद, रेफ़रंस कुछ समय के लिए उपलब्ध रहता है. ऐसा इसलिए होता है, क्योंकि WeakRef
तब तक वैल्यू दिखाता रहता है, जब तक कि उससे जुड़े ऑब्जेक्ट को गै़रबेज इकट्ठा करने की प्रोसेस पूरी नहीं हो जाती. यह प्रोसेस, JavaScript में असिंक्रोनस तरीके से और आम तौर पर, इंतज़ार के समय में होती है. हालांकि, Chrome DevTools के मेमोरी पैनल में, अलग की गई विंडो की जांच करते समय, ढेर के स्नैपशॉट लेने से, असल में गै़रबेज कलेक्शन ट्रिगर होता है और कम रेफ़रंस वाली विंडो को हटा दिया जाता है. यह भी जांचा जा सकता है कि WeakRef
के ज़रिए रेफ़र किए गए ऑब्जेक्ट को JavaScript से हटा दिया गया है या नहीं. इसके लिए, deref()
के undefined
दिखाने पर या नए FinalizationRegistry
API का इस्तेमाल करके पता लगाया जा सकता है:
let popup = new WeakRef(window.open('/login.html'));
// Polling deref():
let timer = setInterval(() => {
if (popup.deref() === undefined) {
console.log('popup was garbage-collected');
clearInterval(timer);
}
}, 20);
// FinalizationRegistry API:
let finalizers = new FinalizationRegistry(() => {
console.log('popup was garbage-collected');
});
finalizers.register(popup.deref());
समाधान: postMessage
पर संपर्क करें
विंडो बंद होने या नेविगेशन के किसी दस्तावेज़ को अनलोड करने का पता लगाने से, हमें हैंडलर हटाने और रेफ़रंस हटाने का तरीका मिलता है. इससे, अलग की गई विंडो को ग़ैर-ज़रूरी डेटा के तौर पर हटाया जा सकता है. हालांकि, ये बदलाव, पेजों के बीच डायरेक्ट कपलिंग की समस्या को ठीक करने के लिए किए गए हैं. यह समस्या कभी-कभी ज़्यादा गंभीर हो सकती है.
एक और बेहतर तरीका उपलब्ध है, जिससे विंडो और दस्तावेज़ों के बीच पुराने रेफ़रंस से बचा जा सकता है: postMessage()
तक क्रॉस-दस्तावेज़ कम्यूनिकेशन को सीमित करके, अलगाव बनाएं. प्रज़ेंटर नोट के हमारे मूल उदाहरण पर वापस आकर, nextSlide()
जैसे फ़ंक्शन, नोट विंडो को सीधे तौर पर अपडेट करते हैं. इसके लिए, वे नोट विंडो का रेफ़रंस लेते हैं और उसके कॉन्टेंट में बदलाव करते हैं. इसके बजाय, मुख्य पेज postMessage()
के ज़रिए, नोट विंडो में ज़रूरी जानकारी को अलग-अलग समय पर और अप्रत्यक्ष तरीके से भेज सकता है.
let updateNotes;
function showNotes() {
// keep the popup reference in a closure to prevent outside references:
let win = window.open('/presenter-view.html');
win.addEventListener('pagehide', () => {
if (!win || !win.location.host) return; // ignore initial "about:blank"
win = null;
});
// other functions must interact with the popup through this API:
updateNotes = (data) => {
if (!win) return;
win.postMessage(data, location.origin);
};
// listen for messages from the notes window:
addEventListener('message', (event) => {
if (event.source !== win) return;
if (event.data[0] === 'nextSlide') nextSlide();
});
}
let slide = 1;
function nextSlide() {
slide += 1;
// if the popup is open, tell it to update without referencing it:
if (updateNotes) {
updateNotes(['setSlide', slide]);
}
}
document.body.onclick = nextSlide;
हालांकि, इसके लिए अब भी ज़रूरी है कि विंडो एक-दूसरे का रेफ़रंस दें, लेकिन कोई भी विंडो, किसी दूसरी विंडो के मौजूदा दस्तावेज़ का रेफ़रंस नहीं रखती. मैसेज पास करने के तरीके से, ऐसे डिज़ाइन बनाने में भी मदद मिलती है जिनमें विंडो के रेफ़रंस एक ही जगह पर रखे जाते हैं. इसका मतलब है कि विंडो बंद करने या किसी दूसरी विंडो पर जाने पर, सिर्फ़ एक रेफ़रंस को अनसेट करना होगा. ऊपर दिए गए उदाहरण में, सिर्फ़ showNotes()
के पास नोट विंडो का रेफ़रंस होता है. साथ ही, यह pagehide
इवेंट का इस्तेमाल करके यह पक्का करता है कि रेफ़रंस को हटा दिया गया है.
समाधान: noopener
का इस्तेमाल करके रेफ़रंस देने से बचें
अगर कोई ऐसी पॉप-अप विंडो खुलती है जिससे आपके पेज को इंटरैक्ट करने या कंट्रोल करने की ज़रूरत नहीं है, तो हो सकता है कि आप कभी भी उस विंडो का रेफ़रंस न पाएं. यह सुविधा खास तौर पर तब फ़ायदेमंद होती है, जब ऐसी विंडो या iframe बनाए जा रहे हों जिनमें किसी दूसरी साइट का कॉन्टेंट लोड किया जाएगा. इन मामलों में, window.open()
"noopener"
विकल्प स्वीकार करता है. यह विकल्प, एचटीएमएल लिंक के लिए rel="noopener"
एट्रिब्यूट की तरह ही काम करता है:
window.open('https://example.com/share', null, 'noopener');
"noopener"
विकल्प की वजह से, window.open()
null
दिखाता है. इससे, पॉप-अप का रेफ़रंस गलती से सेव नहीं हो पाता. इससे पॉप-अप विंडो को अपनी पैरंट विंडो का रेफ़रंस पाने से भी रोका जाता है, क्योंकि window.opener
प्रॉपर्टी null
होगी.
सुझाव/राय दें या शिकायत करें
उम्मीद है कि इस लेख में दिए गए कुछ सुझावों से, मेमोरी लीक का पता लगाने और उसे ठीक करने में मदद मिलेगी. अगर आपके पास अलग-अलग विंडो को डीबग करने के लिए कोई और तरीका है या इस लेख से आपको अपने ऐप्लिकेशन में लीक का पता लगाने में मदद मिली है, तो हमें बताएं! मुझे Twitter पर @_developit पर ढूंढा जा सकता है.