বিচ্ছিন্ন উইন্ডো মেমরি লিক

বিচ্ছিন্ন উইন্ডোগুলির কারণে সৃষ্ট জটিল মেমরি লিকগুলি খুঁজুন এবং ঠিক করুন৷

বারটেক নোয়ারস্কি
Bartek Nowierski

জাভাস্ক্রিপ্টে একটি মেমরি লিক কি?

একটি মেমরি লিক হল সময়ের সাথে সাথে একটি অ্যাপ্লিকেশন দ্বারা ব্যবহৃত মেমরির পরিমাণে অনিচ্ছাকৃত বৃদ্ধি। জাভাস্ক্রিপ্টে, মেমরি লিক হয় যখন বস্তুর আর প্রয়োজন হয় না, কিন্তু তারপরও ফাংশন বা অন্যান্য বস্তুর দ্বারা উল্লেখ করা হয়। এই রেফারেন্সগুলি আবর্জনা সংগ্রহকারীর দ্বারা অপ্রয়োজনীয় বস্তুগুলিকে পুনরুদ্ধার করা থেকে বাধা দেয়।

আবর্জনা সংগ্রহকারীর কাজ হল এমন বস্তুগুলি সনাক্ত করা এবং পুনরুদ্ধার করা যা অ্যাপ্লিকেশন থেকে আর পৌঁছানো যায় না। এটি তখনও কাজ করে যখন বস্তুগুলি নিজেদেরকে রেফারেন্স করে, বা চক্রাকারে একে অপরকে রেফার করে-যখন কোনও অবশিষ্ট রেফারেন্স না থাকে যার মাধ্যমে একটি অ্যাপ্লিকেশন বস্তুর একটি গ্রুপ অ্যাক্সেস করতে পারে, এটি আবর্জনা সংগ্রহ করা যেতে পারে।

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.

মেমরি ফাঁসের একটি বিশেষ জটিল শ্রেণী ঘটে যখন একটি অ্যাপ্লিকেশন এমন বস্তুর উল্লেখ করে যার নিজস্ব জীবনচক্র আছে, যেমন DOM উপাদান বা পপআপ উইন্ডো। এই ধরনের বস্তুর জন্য অ্যাপ্লিকেশন না জেনে অব্যবহৃত হওয়া সম্ভব, যার মানে অ্যাপ্লিকেশন কোডে এমন একটি বস্তুর একমাত্র অবশিষ্ট উল্লেখ থাকতে পারে যা অন্যথায় আবর্জনা সংগ্রহ করা যেতে পারে।

একটি বিচ্ছিন্ন উইন্ডো কি?

নিম্নলিখিত উদাহরণে, একটি স্লাইডশো ভিউয়ার অ্যাপ্লিকেশনে একটি উপস্থাপক নোট পপআপ খুলতে এবং বন্ধ করার বোতামগুলি অন্তর্ভুক্ত রয়েছে৷ কল্পনা করুন যে একজন ব্যবহারকারী নোটগুলি প্রদর্শন করুন ক্লিক করে, তারপরে নোট লুকান বোতামে ক্লিক করার পরিবর্তে সরাসরি পপআপ উইন্ডোটি বন্ধ করে দেয়- 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 অবজেক্টটি এখনও এটি সম্পর্কে তথ্য অ্যাক্সেস করতে ব্যবহার করা যেতে পারে। এটি এক ধরণের বিচ্ছিন্ন উইন্ডো: কারণ জাভাস্ক্রিপ্ট কোড এখনও বন্ধ Window অবজেক্টে সম্ভাব্য বৈশিষ্ট্যগুলি অ্যাক্সেস করতে পারে, এটি অবশ্যই মেমরিতে রাখতে হবে। যদি উইন্ডোটিতে অনেকগুলি জাভাস্ক্রিপ্ট অবজেক্ট বা আইফ্রেম অন্তর্ভুক্ত থাকে, তবে উইন্ডোটির বৈশিষ্ট্যগুলিতে অবশিষ্ট জাভাস্ক্রিপ্ট রেফারেন্স না পাওয়া পর্যন্ত সেই মেমরিটি পুনরুদ্ধার করা যাবে না।

একটি উইন্ডো বন্ধ হওয়ার পরে কীভাবে একটি নথি বজায় রাখা সম্ভব তা প্রদর্শন করতে Chrome DevTools ব্যবহার করে৷

<iframe> উপাদানগুলি ব্যবহার করার সময়ও একই সমস্যা হতে পারে। Iframes নেস্টেড উইন্ডোর মতো আচরণ করে যাতে ডকুমেন্ট থাকে এবং তাদের contentWindow প্রপার্টি ধারণকৃত Window অবজেক্টে অ্যাক্সেস প্রদান করে, অনেকটা window.open() দ্বারা প্রত্যাবর্তিত মানের মতো। জাভাস্ক্রিপ্ট কোড আইফ্রেমের contentWindow বা contentDocument একটি রেফারেন্স রাখতে পারে এমনকি যদি আইফ্রেমটি DOM থেকে সরানো হয় বা এর URL পরিবর্তন করা হয়, যা ডকুমেন্টটিকে আবর্জনা সংগ্রহ করা থেকে বাধা দেয় কারণ এর বৈশিষ্ট্যগুলি এখনও অ্যাক্সেস করা যেতে পারে।

একটি ভিন্ন URL-এ iframe নেভিগেট করার পরেও কীভাবে একটি ইভেন্ট হ্যান্ডলার একটি iframe এর নথি বজায় রাখতে পারে তার প্রদর্শন৷

এমন ক্ষেত্রে যেখানে জাভাস্ক্রিপ্ট থেকে একটি উইন্ডো বা iframe-এর মধ্যে document একটি রেফারেন্স রাখা হয়, সেই নথিটি মেমরিতে রাখা হবে যদিও সেই উইন্ডো বা iframe একটি নতুন URL-এ নেভিগেট করে। এটি বিশেষভাবে সমস্যাজনক হতে পারে যখন সেই রেফারেন্স ধারণ করা জাভাস্ক্রিপ্টটি সনাক্ত করে না যে উইন্ডো/ফ্রেমটি একটি নতুন URL-এ নেভিগেট করেছে, যেহেতু এটি কখন একটি নথি মেমরিতে রেখে শেষ রেফারেন্স হয়ে ওঠে তা জানে না।

কিভাবে বিচ্ছিন্ন উইন্ডো মেমরি লিক কারণ

প্রাথমিক পৃষ্ঠার মতো একই ডোমেনে উইন্ডোজ এবং আইফ্রেমগুলির সাথে কাজ করার সময়, নথির সীমানা জুড়ে ইভেন্টগুলি শোনা বা বৈশিষ্ট্যগুলি অ্যাক্সেস করা সাধারণ। উদাহরণ স্বরূপ, আসুন এই গাইডের শুরু থেকে উপস্থাপনা দর্শক উদাহরণের একটি ভিন্নতা আবার দেখুন। দর্শক স্পিকার নোট প্রদর্শনের জন্য একটি দ্বিতীয় উইন্ডো খোলে। স্পিকার নোট উইন্ডোটি পরবর্তী স্লাইডে যাওয়ার জন্য click ইভেন্টের জন্য তার চিহ্ন হিসাবে শোনে। যদি ব্যবহারকারী এই নোট উইন্ডোটি বন্ধ করে দেয়, তবে মূল প্যারেন্ট উইন্ডোতে চলমান জাভাস্ক্রিপ্টের এখনও স্পিকার নোট নথিতে সম্পূর্ণ অ্যাক্সেস রয়েছে:

<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 রেফারেন্স রয়েছে এর অর্থ হল উইন্ডোটি এখনও রেফারেন্সযুক্ত এবং আবর্জনা সংগ্রহ করা যাবে না।

কিভাবে একটি উইন্ডোর রেফারেন্সগুলি একবার বন্ধ হয়ে গেলে এটিকে আবর্জনা সংগ্রহ করা থেকে বাধা দেয় তার দৃষ্টান্ত।

আরও অনেকগুলি পরিস্থিতি রয়েছে যেখানে রেফারেন্সগুলি দুর্ঘটনাক্রমে ধরে রাখা হয় যা বিচ্ছিন্ন উইন্ডোগুলিকে আবর্জনা সংগ্রহের যোগ্য হতে বাধা দেয়:

  • ইভেন্ট হ্যান্ডলারগুলি একটি আইফ্রেমের প্রাথমিক নথিতে নিবন্ধিত হতে পারে ফ্রেমটি তার উদ্দেশ্যযুক্ত URL-এ নেভিগেট করার আগে, যার ফলে দস্তাবেজে দুর্ঘটনাজনিত রেফারেন্স এবং অন্যান্য রেফারেন্সগুলি পরিষ্কার করার পরে আইফ্রেমটি টিকে থাকে৷

  • একটি উইন্ডো বা আইফ্রেমে লোড করা একটি মেমরি-ভারী নথি ভুলবশত একটি নতুন URL এ নেভিগেট করার পরে অনেকক্ষণ মেমরিতে রাখা যেতে পারে। এটি প্রায়ই শ্রোতা অপসারণের অনুমতি দেওয়ার জন্য নথিতে মূল পৃষ্ঠার রেফারেন্স ধরে রাখার কারণে ঘটে।

  • একটি জাভাস্ক্রিপ্ট অবজেক্টকে অন্য উইন্ডো বা আইফ্রেমে পাস করার সময়, অবজেক্টের প্রোটোটাইপ চেইনে এটি তৈরি করা উইন্ডো সহ পরিবেশের উল্লেখ থাকে। এর মানে অন্য উইন্ডোজ থেকে বস্তুর রেফারেন্স রাখা এড়াতে যতটা গুরুত্বপূর্ণ, ঠিক ততটাই গুরুত্বপূর্ণ যেভাবে উইন্ডোজের রেফারেন্স রাখা এড়ানো।

    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-এ মেমরি ট্যাবে যান এবং উপলব্ধ প্রোফাইলিং প্রকারের তালিকায় Heap Snapshot নির্বাচন করুন৷ রেকর্ডিং শেষ হয়ে গেলে, সারাংশ ভিউ বর্তমান অবজেক্ট ইন-মেমরি দেখায়, কনস্ট্রাক্টর দ্বারা গোষ্ঠীবদ্ধ।

Chrome DevTools-এ একটি হিপ স্ন্যাপশট নেওয়ার প্রদর্শন।

হিপ ডাম্প বিশ্লেষণ করা একটি কঠিন কাজ হতে পারে এবং ডিবাগিংয়ের অংশ হিসাবে সঠিক তথ্য খুঁজে পাওয়া বেশ কঠিন হতে পারে। এটিতে সাহায্য করার জন্য, Chromium ইঞ্জিনিয়াররা yossik@ এবং peledni@ একটি স্বতন্ত্র হিপ ক্লিনার টুল তৈরি করেছে যা একটি বিচ্ছিন্ন উইন্ডোর মতো একটি নির্দিষ্ট নোডকে হাইলাইট করতে সাহায্য করতে পারে৷ একটি ট্রেসে হিপ ক্লিনার চালানো ধারণ গ্রাফ থেকে অন্যান্য অপ্রয়োজনীয় তথ্য সরিয়ে দেয়, যা ট্রেসটিকে ক্লিনার এবং পড়া সহজ করে তোলে।

প্রোগ্রাম্যাটিকভাবে মেমরি পরিমাপ করুন

হিপ স্ন্যাপশটগুলি একটি উচ্চ স্তরের বিশদ প্রদান করে এবং কোথায় ফাঁস হয় তা খুঁজে বের করার জন্য দুর্দান্ত, তবে একটি হিপ স্ন্যাপশট নেওয়া একটি ম্যানুয়াল প্রক্রিয়া। মেমরি লিক চেক করার আরেকটি উপায় হল performance.memory API থেকে বর্তমানে ব্যবহৃত জাভাস্ক্রিপ্ট হিপ সাইজ পাওয়া:

Chrome DevTools ব্যবহারকারী ইন্টারফেসের একটি বিভাগের একটি স্ক্রিনশট।
DevTools-এ ব্যবহৃত JS হিপ সাইজ চেক করা হচ্ছে একটি পপআপ তৈরি, বন্ধ এবং রেফারেন্সবিহীন।

performance.memory API শুধুমাত্র JavaScript হিপ সাইজ সম্পর্কে তথ্য প্রদান করে, যার মানে এটি পপআপের নথি এবং সংস্থান দ্বারা ব্যবহৃত মেমরি অন্তর্ভুক্ত করে না। সম্পূর্ণ ছবি পেতে, আমাদের নতুন performance.measureUserAgentSpecificMemory() API ব্যবহার করতে হবে যা বর্তমানে Chrome এ ট্রায়াল করা হচ্ছে।

বিচ্ছিন্ন উইন্ডো লিক এড়ানোর জন্য সমাধান

দুটি সবচেয়ে সাধারণ ক্ষেত্রে যেখানে বিচ্ছিন্ন উইন্ডোগুলি মেমরি ফাঁসের কারণ হয় যখন প্যারেন্ট ডকুমেন্ট একটি বন্ধ পপআপ বা সরানো আইফ্রেমের রেফারেন্স ধরে রাখে এবং যখন একটি উইন্ডো বা 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 আবদ্ধ রেফারেন্স মানে এটি আবর্জনা-সংগ্রহ করা যাবে না।

সমাধান: আনসেট রেফারেন্স

অন্য উইন্ডো বা এর নথির উল্লেখ করে এমন ভেরিয়েবলগুলি এটিকে মেমরিতে ধরে রাখে। যেহেতু জাভাস্ক্রিপ্টের অবজেক্টগুলি সর্বদা রেফারেন্স হয়, তাই ভেরিয়েবলের জন্য একটি নতুন মান বরাদ্দ করলে মূল বস্তুর রেফারেন্স মুছে যায়। একটি বস্তুর রেফারেন্স "আনসেট" করতে, আমরা সেই ভেরিয়েবলগুলিকে মান null এ পুনরায় বরাদ্দ করতে পারি।

পূর্ববর্তী পপআপ উদাহরণে এটি প্রয়োগ করে, আমরা ক্লোজ বোতাম হ্যান্ডলারটিকে পপআপ উইন্ডোতে এর রেফারেন্স "আনসেট" করতে পরিবর্তন করতে পারি:

let popup;
open.onclick = () => {
  popup = window.open('/login.html');
};
close.onclick = () => {
  popup.close();
  popup = null;
};

এটি সাহায্য করে, তবে open() ব্যবহার করে তৈরি করা উইন্ডোগুলির জন্য নির্দিষ্ট একটি সমস্যা প্রকাশ করে: ব্যবহারকারী যদি আমাদের কাস্টম বন্ধ বোতামে ক্লিক করার পরিবর্তে উইন্ডোটি বন্ধ করে দেয় তবে কী হবে? আরও এখনও, ব্যবহারকারী যদি আমাদের খোলা উইন্ডোতে অন্য ওয়েবসাইটগুলিতে ব্রাউজ করা শুরু করে তবে কী হবে? যদিও আমাদের ক্লোজ বোতামে ক্লিক করার সময় popup রেফারেন্সটি আনসেট করার জন্য এটি যথেষ্ট বলে মনে হয়েছিল, ব্যবহারকারীরা যখন উইন্ডোটি বন্ধ করতে সেই নির্দিষ্ট বোতামটি ব্যবহার করেন না তখনও একটি মেমরি লিক থাকে। এটি সমাধান করার জন্য এই কেসগুলি সনাক্ত করা প্রয়োজন যাতে সেগুলি ঘটলে দীর্ঘায়িত রেফারেন্সগুলি আনসেট করা যায়৷

সমাধান: মনিটর এবং নিষ্পত্তি

অনেক পরিস্থিতিতে, জাভাস্ক্রিপ্ট উইন্ডো খোলার জন্য বা ফ্রেম তৈরির জন্য দায়ী তাদের জীবনচক্রের উপর একচেটিয়া নিয়ন্ত্রণ থাকে না। পপআপ ব্যবহারকারীর দ্বারা বন্ধ হয়ে যেতে পারে, অথবা একটি নতুন নথিতে নেভিগেশনের ফলে পূর্বে একটি উইন্ডো বা ফ্রেমে থাকা নথিটি বিচ্ছিন্ন হয়ে যেতে পারে। উভয় ক্ষেত্রেই, নথিটি আনলোড করা হচ্ছে তা সংকেত দিতে ব্রাউজার একটি pagehide ইভেন্ট ফায়ার করে।

pagehide ইভেন্টটি বর্তমান নথি থেকে বন্ধ উইন্ডো এবং নেভিগেশন সনাক্ত করতে ব্যবহার করা যেতে পারে। যাইহোক, একটি গুরুত্বপূর্ণ সতর্কতা রয়েছে: সমস্ত নতুন-তৈরি করা উইন্ডো এবং আইফ্রেমে একটি খালি নথি থাকে, তারপর দেওয়া থাকলে অসিঙ্ক্রোনাসভাবে প্রদত্ত URL-এ নেভিগেট করুন। ফলস্বরূপ, একটি প্রাথমিক pagehide ইভেন্ট টার্গেট ডকুমেন্ট লোড হওয়ার ঠিক আগে, উইন্ডো বা ফ্রেম তৈরি করার পরপরই ফায়ার করা হয়। যেহেতু টার্গেট ডকুমেন্ট আনলোড করার সময় আমাদের রেফারেন্স ক্লিনআপ কোড চালানো দরকার, তাই আমাদের এই প্রথম pagehide ইভেন্টটিকে উপেক্ষা করতে হবে। এটি করার জন্য অনেকগুলি কৌশল রয়েছে, যার মধ্যে সবচেয়ে সহজ হল প্রাথমিক নথির about:blank URL থেকে উদ্ভূত পেজহাইড ইভেন্টগুলিকে উপেক্ষা করা। আমাদের পপআপ উদাহরণে এটি দেখতে কেমন হবে তা এখানে:

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 ব্যবহার করুন

জাভাস্ক্রিপ্ট সম্প্রতি বস্তুর উল্লেখ করার একটি নতুন উপায়ের জন্য সমর্থন অর্জন করেছে যা আবর্জনা সংগ্রহের অনুমতি দেয়, যাকে 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 একটি মান ফেরত দিতে থাকে যতক্ষণ না তার সম্পর্কিত বস্তুটি আবর্জনা-সংগ্রহ না করা হয়, যা জাভাস্ক্রিপ্টে অ্যাসিঙ্ক্রোনাসভাবে ঘটে এবং সাধারণত নিষ্ক্রিয় সময়ে ঘটে। সৌভাগ্যক্রমে, Chrome DevTools মেমরি প্যানেলে বিচ্ছিন্ন উইন্ডোগুলি পরীক্ষা করার সময়, একটি স্তূপ স্ন্যাপশট নেওয়া আসলে আবর্জনা সংগ্রহকে ট্রিগার করে এবং দুর্বলভাবে রেফারেন্সযুক্ত উইন্ডোটি নিষ্পত্তি করে৷ WeakRef এর মাধ্যমে উল্লেখ করা একটি বস্তু জাভাস্ক্রিপ্ট থেকে নিষ্পত্তি করা হয়েছে কিনা তা পরীক্ষা করাও সম্ভব, হয় 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 ব্যবহার করে রেফারেন্স এড়িয়ে চলুন

এমন ক্ষেত্রে যেখানে একটি পপআপ উইন্ডো খোলা হয় যে আপনার পৃষ্ঠার সাথে যোগাযোগ বা নিয়ন্ত্রণের প্রয়োজন নেই, আপনি উইন্ডোটির রেফারেন্স পাওয়া এড়াতে সক্ষম হতে পারেন। উইন্ডোজ বা আইফ্রেম তৈরি করার সময় এটি বিশেষভাবে কার্যকর যা অন্য সাইট থেকে সামগ্রী লোড করবে। এই ক্ষেত্রে, window.open() একটি "noopener" বিকল্প গ্রহণ করে যা HTML লিঙ্কগুলির জন্য rel="noopener" বৈশিষ্ট্যের মতো কাজ করে:

window.open('https://example.com/share', null, 'noopener');

"noopener" বিকল্পটি window.open() কে null ফেরত দেয়, যা ভুলবশত পপআপের একটি রেফারেন্স সংরক্ষণ করা অসম্ভব করে তোলে। এটি পপআপ উইন্ডোটিকে তার মূল উইন্ডোর একটি রেফারেন্স পেতে বাধা দেয়, যেহেতু window.opener বৈশিষ্ট্য null হবে।

প্রতিক্রিয়া

আশা করি এই নিবন্ধের কিছু পরামর্শ মেমরি লিক খুঁজে পেতে এবং ঠিক করতে সাহায্য করবে। যদি আপনার কাছে বিচ্ছিন্ন উইন্ডোগুলি ডিবাগ করার জন্য অন্য কোনও কৌশল থাকে বা এই নিবন্ধটি আপনার অ্যাপে ফাঁস উন্মোচন করতে সহায়তা করে, আমি জানতে চাই! আপনি আমাকে টুইটারে খুঁজে পেতে পারেন @_developit