تسرّب ذاكرة النافذة المنفصلة

يمكنك العثور على مشاكل تسرُّب الذاكرة الصعبة التي تسببت فيها النوافذ المنفصلة وإصلاحها.

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.

تحدث فئة صعبة بشكل خاص من تسرب الذاكرة عندما يشير تطبيق إلى كائنات لها دورة حياة خاصة بها، مثل عناصر 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 الذي تم عرضه من window.open() للوصول إلى معلومات عنه. وهذا أحد أنواع النوافذ المنفصلة: يجب الاحتفاظ به في الذاكرة لأنّه لا يزال بإمكان رمز JavaScript الوصول إلى الخصائص في عنصر Window المغلق. فإذا تضمّنت النافذة الكثير من كائنات JavaScript أو إطارات iframe، فلا يمكن استرداد هذه الذاكرة إلى أن لا تتبقّ أي مراجع JavaScript لخصائص النافذة.

استخدام "أدوات مطوري البرامج في Chrome" لتوضيح كيفية إمكانية الاحتفاظ بمستند بعد إغلاق نافذة.

يمكن أن تحدث المشكلة نفسها أيضًا عند استخدام عناصر <iframe>. تعمل إطارات Iframe مثل النوافذ المتداخلة التي تحتوي على مستندات، وتوفّر السمة contentWindow الخاصة بها إمكانية الوصول إلى كائن Window المضمّن، مثل القيمة التي تعرضها window.open() إلى حد كبير. يمكن لرمز JavaScript الاحتفاظ بمرجع إلى contentWindow أو contentDocument لإطار iframe حتى إذا تمت إزالة إطار iframe من نموذج العناصر في المستند (DOM) أو من خلال تغييرات عناوين URL الخاصة به، ما يمنع جمع البيانات غير الصالحة في المستند، لأنّه لا يزال من الممكن الوصول إلى خصائصه.

عرض توضيحي لكيفية الاحتفاظ بمستند إطار iframe، حتى بعد نقل إطار iframe إلى عنوان URL مختلف

في الحالات التي يتم فيها الاحتفاظ بمرجع إلى document داخل نافذة أو إطار iframe من JavaScript، سيتم الاحتفاظ بهذا المستند في الذاكرة حتى إذا انتقلت النافذة التي تتضمّن الصورة أو إطار iframe إلى عنوان URL جديد. قد يكون ذلك مزعجًا بشكل خاص عندما لا يكتشف رمز JavaScript الذي يحمل هذا المرجع أنّ النافذة/الإطار قد انتقلوا إلى عنوان URL جديد، لأنّه لا يعرف متى يصبح آخر مرجع يحفظ المستند في الذاكرة.

كيفية تسبّب النوافذ المنفصلة في تسرّب الذاكرة

عند العمل باستخدام النوافذ وإطارات 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 قبل انتقال الإطار إلى عنوان URL المقصود، ما يؤدي إلى إرسال إشارات غير مقصودة إلى المستند واستمرار إطار iframe بعد إزالة المراجع الأخرى.

  • يمكن الاحتفاظ عن طريق الخطأ بالمستند المثقل بالذاكرة والذي تم تحميله في نافذة أو iframe في الذاكرة لفترة طويلة بعد الانتقال إلى عنوان URL جديد. غالبًا ما يحدث هذا بسبب احتفاظ الصفحة الرئيسية بالمراجع للمستند للسماح بإزالة المستمع.

  • عند تمرير كائن 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>
    

رصد تسرُّب الذاكرة الناتج عن فصل النوافذ

قد يكون تتبُّع تسرُّب الذاكرة أمرًا صعبًا. غالبًا ما يكون من الصعب إنشاء نسخ معزولة لهذه المشكلات، خاصةً عند تضمين مستندات أو نوافذ متعددة. لجعل الأمور أكثر تعقيدًا، يمكن أن يؤدي فحص المراجع المحتملة المسرّبة إلى إنشاء مراجع إضافية تمنع جمع العناصر التي تم فحصها من بيانات غير صحيحة. لتحقيق هذه الغاية، من المفيد أن تبدأ بالأدوات التي تتجنب على وجه التحديد تقديم هذه الاحتمالية.

يُعدّ أخذ لقطة متعدّدة مكانًا رائعًا لبدء تصحيح مشاكل الذاكرة. ويوفّر ذلك عرض نقطة زمنية في الذاكرة التي يستخدمها أحد التطبيقات حاليًا، أي جميع الكائنات التي تم إنشاؤها ولكن لم يتم جمعها من البيانات المهملة بعد. تحتوي اللقطات الشامل على معلومات مفيدة حول الكائنات، بما في ذلك حجمها وقائمة بالمتغيرات والإغلاقات التي تشير إليها.

لقطة شاشة للقطة مجمّعة في &quot;أدوات مطوري البرامج في Chrome&quot; تعرض المراجع التي تحتفظ بعنصر كبير
لقطة مجمّعة تعرض المراجع التي تحتفظ بكائن كبير.

لتسجيل لقطة مجمّعة، انتقِل إلى علامة التبويب الذاكرة في "أدوات مطوري البرامج في Chrome" واختَر نبذة عنية في قائمة أنواع الملفات التعريفية المتوفّرة. بعد انتهاء التسجيل، تعرض طريقة عرض الملخّص العناصر الحالية في الذاكرة، وتكون مجمّعة حسب الدالة الإنشائية.

عرض توضيحي لأخذ لقطة مجمّعة في "أدوات مطوري البرامج في Chrome"

قد يكون تحليل تفريغ الذاكرة مهمة شاقة، وقد يكون من الصعب جدًا العثور على المعلومات الصحيحة كجزء من تصحيح الأخطاء. للمساعدة في ذلك، طوّر مهندسا Chromium yossik@ وpeledni@ أداة Hap Cleaner مستقلّة للمساعدة في إبراز عقدة معيّنة، مثل نافذة منفصلة. يؤدي تشغيل أداة Heap Cleaner إلى بيانات التنقّل في مسار الزحف إلى إزالة المعلومات الأخرى غير الضرورية من الرسم البياني للاحتفاظ بالبيانات، ما يجعل التتبُّع أكثر وضوحًا وأسهل في القراءة.

قياس الذاكرة آليًا

توفّر اللقطات المجمّعة مستوى عالٍ من التفاصيل وهي ممتازة لمعرفة أماكن حدوث التسريبات، إلا أنّ أخذ لقطة مجمّعة هو عملية يدوية. هناك طريقة أخرى للتحقّق من تسرُّب الذاكرة، وهي الحصول على حجم كومة الذاكرة المؤقتة المستخدَم حاليًا من performance.memory API:

لقطة شاشة لقسم من واجهة مستخدم &quot;أدوات مطوري البرامج في Chrome&quot;.
يتم إنشاء نافذة منبثقة وإغلاقها بدون الإشارة إلى حجم عناصر 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 التي تمّ إنشاؤها حديثًا على مستند فارغ، ثمّ انتقِل بشكل غير متزامن إلى عنوان URL المحدّد إذا تمّ توفيره. ونتيجةً لذلك، يتم تنشيط حدث pagehide الأولي بعد وقت قصير من إنشاء النافذة أو الإطار، وقبل تحميل المستند المستهدَف مباشرةً. بما أنّه يجب تشغيل رمز تنظيف المرجع عند إلغاء تحميل مستند target، علينا تجاهل حدث pagehide الأول هذا. يتوفّر عدد من الأساليب لإجراء ذلك، وأبسطها هو تجاهل أحداث إخفاء الصفحة الناشئة عن عنوان URL الخاص بـ 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، يمكنك الوصول إلى النافذة على النحو المطلوب. عند إغلاق النافذة، قد يتم تجميع بيانات غير صحيحة، ما يؤدي إلى بدء عرض علامة undefined في طريقة .deref().

<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"، يؤدي التقاط لقطة مجمّعة إلى ظهور تجميع البيانات المهملة والتخلص من النافذة ذات الإشارة الضعيفة. ويمكن أيضًا التحقق من أنّ كائنًا تمت الإشارة إليه عبر 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" لروابط HTML:

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

يؤدي الخيار "noopener" إلى عرض window.open() للسمة null، ما يجعل من المستحيل تخزين مرجع إلى النافذة المنبثقة عن طريق الخطأ. ويمنع أيضًا النافذة المنبثقة من الحصول على مرجع إلى نافذتها الرئيسية، لأنّ السمة window.opener ستكون null.

إضافة ملاحظات

نأمل أن تكون بعض الاقتراحات الواردة في هذه المقالة مفيدة في العثور على عمليات تسرّب الذاكرة وإصلاحها. إذا كانت لديك تقنية أخرى لتصحيح أخطاء النوافذ المنفصلة أو ساعدت هذه المقالة في الكشف عن تسرب في تطبيقك، فأنا أريد أن أعرف ذلك! يمكنك التواصل معي على Twitter @_developit.