تقنية

قوائم DOM الحية: لماذا تتخطى الحلقة عنصرًا عند إزالة الصنف؟

خروج أ ينقل ب إلى الفهرس صفر؛ العداد يتقدم إلى واحد فيتجاوز ب.
رسم مؤلف لتغيّر العضوية؛ إزالة الصنف من البطاقة لا تحذف البطاقة من الصفحة. [1] [2]

قد تبدأ بثلاثة عناصر تحمل الصنف نفسه، ثم تكتشف أن حلقة إزالة الصنف تركت العنصر الأوسط. افحص نوع القائمة التي تمر عليها: HTMLCollection قائمة حية، فيتغير طولها وترتيب أعضائها مع تغيّر العناصر المطابقة. [4]

أنشئ مصفوفة باستخدام Array.from قبل حلقة التعديل لتثبيت العناصر التي ستمر عليها، بدل متابعة قائمة تتغير أثناء التنفيذ. [3] [4]

الخلاصة السريعة

  • قائمة HTMLCollection الحية تعكس العناصر المطابقة حاليًا؛ حفظها في متغير لا يثبت عضويتها. [4]
  • إزالة الصنف الذي بُني عليه البحث تخرج العنصر من القائمة فورًا؛ قد ينتقل التالي إلى فهرس تجاوزته الحلقة. [2]
  • تُنشئ Array.from مصفوفة جديدة قبل التعديل؛ مرّ على هذه المصفوفة لتنفيذ الإزالة على كل عنصر جُمع فيها. [3]
  • النسخة تثبت أعضاء القائمة، لكنها تبقي مراجع عناصر DOM الأصلية؛ تغييرات العناصر نفسها تظل مرئية عبر هذه المراجع. [1]

راقب العضوية قبل الحلقة

قائمة HTMLCollection الحية تعكس العناصر المطابقة حاليًا؛ حفظها في متغير لا يثبت عضويتها. [4]

يمكن قراءة عدد العناصر من length والعنصر من فهرسه. في القائمة الحية، لا تفترض أن الفهرس يشير إلى العنصر نفسه بعد تعديل الوثيقة. سنستخدم ثلاث بطاقات مرتبة، ونغيّر الصنف فقط مع إبقاء البطاقات في الصفحة. [4]

مرّر الشيفرة أفقيًا عند الحاجة.

<div id="panel">
  <p id="card-a" class="pending">بطاقة أ</p>
  <p id="card-b" class="pending">بطاقة ب</p>
  <p id="card-c" class="pending">بطاقة ج</p>
</div>

لماذا بقي العنصر الأوسط؟

إزالة الصنف الذي بُني عليه البحث تخرج العنصر من القائمة فورًا؛ قد ينتقل التالي إلى فهرس تجاوزته الحلقة. [2]

عند إزالة الصنف من أ، تنتقل ب إلى صفر وج إلى واحد. لكن العداد يرتفع إلى واحد، فيعالج ج ويتجاوز ب. [2]

مرّر الشيفرة أفقيًا عند الحاجة.

const panel = document.getElementById("panel");
const live = panel.getElementsByClassName("pending");
for (let i = 0; i < live.length; i++) {
  live[i].classList.remove("pending");
}
console.log(live[0].id); // card-b

ثبّت القائمة قبل تعديل العناصر

تُنشئ Array.from مصفوفة جديدة قبل التعديل؛ مرّ على هذه المصفوفة لتنفيذ الإزالة على كل عنصر جُمع فيها. [3]

أعد تحميل عينة HTML أولًا حتى تعود الأصناف الثلاثة. حوّل المجموعة مرة واحدة قبل الحلقة، ثم عدّل العناصر الموجودة في المصفوفة. إنشاء النسخة بعد الحلقة الأولى لن يعيد البطاقات التي خرجت من نتيجة البحث. [3] [4]

مرّر الشيفرة أفقيًا عند الحاجة.

const panel = document.getElementById("panel");
const live = panel.getElementsByClassName("pending");
const fixed = Array.from(live);
for (const card of fixed) {
  card.classList.remove("pending");
}
console.log(fixed.length, live.length); // 3 0

اختبر العضوية وحالة العنصر منفصلتين

النسخة تثبت أعضاء القائمة، لكنها تبقي مراجع عناصر DOM الأصلية؛ تغييرات العناصر نفسها تظل مرئية عبر هذه المراجع. [1]

بعد الحل تبقى المصفوفة بطول ثلاثة، بينما تخلو القائمة الحية من المطابقات. إذا قرأت className للعنصر الأول عبر fixed ستجد أن الصنف أزيل؛ لم نحفظ صورة قديمة لحالة البطاقة. وتبقى العناصر الثلاثة في الوثيقة لأننا عدّلنا صنفًا ولم نحذف عقدة. [1]

وللتحقق من حدود النسخة، أضف لاحقًا بطاقة جديدة تحمل pending: تظهر في القائمة الحية، ولا تُضاف تلقائيًا إلى المصفوفة السابقة. إذا كانت مهمتك تشمل العناصر الجديدة أيضًا، فحدّد موعد بحث جديد بدل اعتبار النسخة قائمة متابعة مستمرة. [1]

مرّر الشيفرة أفقيًا عند الحاجة.

console.log(fixed[0] === panel.children[0]); // true
console.log(fixed[0].className); // ""
console.log(panel.children.length); // 3

المصادر ومتابعة القراءة

  1. MDN: التعامل مع مجموعات العناصر (يفتح في نافذة جديدة)developer.mozilla.org
  2. MDN: البحث عن العناصر حسب الصنف (يفتح في نافذة جديدة)developer.mozilla.org
  3. MDN: إنشاء مصفوفة باستخدام Array.from (يفتح في نافذة جديدة)developer.mozilla.org
  4. MDN: واجهة HTMLCollection (يفتح في نافذة جديدة)developer.mozilla.org

أُعدّ هذا المقال بصياغة عربية أصلية بالاستناد إلى المصادر أعلاه، وهو مدخل تمهيدي إلى الموضوع. اقرأ منهجية المحتوى وحدوده.