अलग विंडो मेमोरी लीक

डिटैच की गई विंडो की वजह से होने वाली मेमोरी में हुई गड़बड़ी का पता लगाकर उसे ठीक करें.

Bartek Nowierski
Bartek Nowierski

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 के रेफ़रंस बचे नहीं रह जाते.

Chrome DevTools का इस्तेमाल करके यह दिखाना कि विंडो बंद करने के बाद, किसी दस्तावेज़ को कैसे सेव रखा जा सकता है.

<iframe> एलिमेंट इस्तेमाल करते समय भी यही समस्या आ सकती है. Iframe, नेस्ट की गई उन विंडो की तरह काम करते हैं जिनमें दस्तावेज़ होते हैं. साथ ही, उनकी contentWindow प्रॉपर्टी, window.open() से मिलने वाली वैल्यू की तरह ही, शामिल Window ऑब्जेक्ट का ऐक्सेस देती है. JavaScript कोड, iframe को contentWindow या contentDocument का रेफ़रंस दे सकता है, भले ही iframe को डीओएम से हटा दिया गया हो या उसके यूआरएल में बदलाव कर दिया गया हो. इस वजह से, दस्तावेज़ का मिटाया हुआ कॉन्टेंट नहीं दिखता, क्योंकि उसकी प्रॉपर्टी अब भी ऐक्सेस की जा सकती हैं.

इस वीडियो में दिखाया गया है कि इवेंट हैंडलर, iframe के दस्तावेज़ को कैसे सेव रख सकता है. ऐसा iframe को किसी दूसरे यूआरएल पर नेविगेट करने के बाद भी किया जा सकता है.

ऐसे मामलों में जहां विंडो या iframe में document का संदर्भ JavaScript से सेव रहता है, तो उस दस्तावेज़ को मेमोरी में रखा जाएगा, भले ही वह दस्तावेज़ उस विंडो या iframe को नए यूआरएल पर ले जाए. यह खास तौर पर तब परेशानी पैदा कर सकता है, जब उस रेफ़रंस को होल्ड करने वाला JavaScript यह पता नहीं लगा पाता कि विंडो/फ़्रेम ने किसी नए यूआरएल पर नेविगेट किया है, क्योंकि उसे यह पता नहीं चलता कि किसी दस्तावेज़ को मेमोरी में रखने वाला आखिरी रेफ़रंस कब बना.

डिटैच की गई विंडो से मेमोरी लीक कैसे होती है

मुख्य पेज वाले डोमेन पर Windows और iframes के साथ काम करते समय, आम तौर पर दस्तावेज़ की सीमाओं में इवेंट को सुनना या प्रॉपर्टी ऐक्सेस करना आम बात है. उदाहरण के लिए, इस गाइड की शुरुआत से प्रज़ेंटेशन व्यूअर के उदाहरण की एक वैरिएशन पर फिर से नज़र डालते हैं. प्रज़ेंटर के नोट दिखाने के लिए दर्शक दूसरी विंडो खोलता है. स्पीकर नोट विंडो, अगली स्लाइड पर जाने के लिए 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 में हीप स्नैपशॉट का स्क्रीनशॉट, जिसमें ऐसे रेफ़रंस दिखाए गए हैं जो किसी बड़े ऑब्जेक्ट को बनाए रखते हैं.
एक हीप स्नैपशॉट, जिसमें उन रेफ़रंस को दिखाया गया है जो किसी बड़े ऑब्जेक्ट को बनाए रखते हैं.

हीप स्नैपशॉट रिकॉर्ड करने के लिए, Chrome DevTools में मेमोरी टैब पर जाएं और उपलब्ध प्रोफ़ाइलिंग टाइप की सूची में हीप स्नैपशॉट को चुनें. रिकॉर्डिंग खत्म हो जाने के बाद, खास जानकारी व्यू में मौजूदा ऑब्जेक्ट को मेमोरी में दिखाया जाता है. इन्हें कंस्ट्रक्टर के हिसाब से ग्रुप में बांटा जाता है.

Chrome DevTools में हीप स्नैपशॉट लेने का तरीका.

हीप डंप का विश्लेषण करना मुश्किल काम हो सकता है और डीबग करने के दौरान सही जानकारी ढूंढना काफ़ी मुश्किल हो सकता है. इस काम में मदद करने के लिए, Chromium के इंजीनियरों yossik@ और peledni@, ने एक स्टैंडअलोन हीप क्लीनर टूल डेवलप किया. यह टूल, डिटैच्ड विंडो जैसे खास नोड को हाइलाइट करने में मदद कर सकता है. ट्रेस पर हीप क्लीनर चलाने से रिटेंशन ग्राफ़ से गैर-ज़रूरी जानकारी हटा दी जाती है. इससे ट्रेस और ज़्यादा आसानी से पढ़ा जा सकता है.

प्रोग्राम के हिसाब से, मेमोरी को मेज़र करना

हीप स्नैपशॉट में बेहतर तरीके से जानकारी दी जाती है और यह पता लगाने में बेहतरीन सुविधा मिलती है कि लीक कहां पर हो रही है. हालांकि, हीप स्नैपशॉट लेना एक मैन्युअल प्रोसेस है. मेमोरी लीक का पता लगाने का एक और तरीका यह है कि फ़िलहाल इस्तेमाल किए जा रहे JavaScript हीप साइज़ को performance.memory API से पाया जाए:

Chrome DevTools के यूज़र इंटरफ़ेस के एक सेक्शन का स्क्रीनशॉट.
पॉप-अप बनाए जाने, बंद करने, और उसका रेफ़रंस नहीं दिए जाने के तौर पर, DevTools में इस्तेमाल किए गए JS हीप साइज़ की जांच की जा रही है.

performance.memory एपीआई सिर्फ़ JavaScript के हीप साइज़ के बारे में जानकारी देता है. इसका मतलब है कि इसमें पॉप-अप के दस्तावेज़ और संसाधनों में इस्तेमाल की गई मेमोरी शामिल नहीं है. पूरी जानकारी पाने के लिए, हमें Chrome में फ़िलहाल आज़माए जा रहे नए performance.measureUserAgentSpecificMemory() एपीआई का इस्तेमाल करना होगा.

डिटैच्ड विंडो लीक से बचने के लिए समाधान

दो सबसे आम मामले, जिनमें डिटैच की गई विंडो की वजह से मेमोरी लीक होती है वे हैं जब पैरंट दस्तावेज़, बंद पॉप-अप या हटाए गए 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 यूआरएल से शुरू होने वाले pagehide इवेंट को अनदेखा करना. यहां बताया गया है कि यह हमारे पॉप-अप उदाहरण में कैसा दिखेगा:

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 एपीआई का इस्तेमाल करके:

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 @_develoपिट पर ढूंढा जा सकता है.