• בלוג
  • הקסם הסודי של DOM NodeList

הקסם הסודי של DOM NodeList

09/06/2020

אוביקט NodeList הוא מה שחוזר מפונקציות רבות ב DOM שמחזירות קבוצה של אלמנטים, אבל מעניין לראות שיש שני סוגים של NodeList עם הבדל מהותי ביניהם.

1. קוד לדוגמא

בשביל שנבין על מה אנחנו מדברים הנה קובץ HTML שמציג רשימת פריטים עם שני כפתורים:

<button id="add">Add Stuff</button>
<button id="clear">Reset</button>
<p>Total: <span id="total"></span></p>
<ul>
  <li>1</li>
  <li>2</li>
</ul>

ולידו קובץ JavaScript שמוסיף התנהגות לכפתורים:

const ul = document.querySelector('ul');
calcTotalItems();  

add.addEventListener('click', function() {
  const li = document.createElement('li');
  li.textContent = Math.floor(Math.random() * 100);
  ul.appendChild(li);
  calcTotalItems();  
});

clear.addEventListener('click', function() {
  ul.innerHTML = '';
  calcTotalItems();
});

function calcTotalItems() {
  const items = document.querySelectorAll('li');
  total.textContent = items.length;
}

יש גם קודפן למי שאוהב לראות את הקוד בפעולה לפני שמתחילים לדבר עליו:

הפונקציה calcTotalItems סופרת כמה li-ים יש ומעדכנת את המספר על המסך:

function calcTotalItems() {
  const items = document.querySelectorAll('li');
  total.textContent = items.length;
}

אני קורא לה מכל מיני נקודות במערכת כדי שמספר הפריטים שמופיע תמיד יהיה מדויק.

2. נודליסט סטטי

השורה הראשונה של הפונקציה מחזירה אוביקט שנקרא NodeList לתוך המשתנה items, ובגלל שמדובר ב querySelectorAll קיבלנו נודליסט סטטי - כלומר כזה שלא מושפע משינויי DOM שיקרו בעתיד.

בואו נראה מה יקרה אם אוציא את השורה החוצה מהפונקציה לראש הקובץ, כלומר קובץ ה JavaScript יראה כך:

const ul = document.querySelector('ul');
const items = document.querySelectorAll('li');
calcTotalItems();

add.addEventListener('click', function() {
  const li = document.createElement('li');
  li.textContent = Math.floor(Math.random() * 100);
  ul.appendChild(li);
  calcTotalItems();  
});

clear.addEventListener('click', function() {
  ul.innerHTML = '';
  calcTotalItems();
});

function calcTotalItems() {
  total.textContent = items.length;
}

ניסיון להשתמש בתוכנית עכשיו יהיה קצת מאכזב: המספר לא ישתנה לא משנה כמה פריטים נוסיף או נמחק. הפריטים ב items מתאימים לפריטים שהיו ב DOM ברגע שהפעלנו את querySelectorAll.

זה נחמד, כי זה אומר שאם יש לנו רשימת פריטים מה DOM ואנחנו רוצים לרוץ עליהם בלולאה אנחנו יכולים לכתוב את הקוד הבא:

const items = document.querySelectorAll('li');

for (let i=0; i < items.length; i++) {
    const item = items[i];
    console.log(item.textContent);
}

ואנחנו לא משלמים מחיר מבחינת ביצועים שכן באופן אוטומטי הדפדפן זוכר את ה length של ה NodeList ולא באמת מחשב אותו מחדש עם כל איטרציה.

3. נודליסט דינמי

אבל יש ב DOM דרכים נוספות ליצור NodeList ולא כולן יוצרות את אותה התנהגות. הפקודה children של אלמנט מחזירה NodeList דינמי. זה נודליסט שמייצג שאילתה חיה ל DOM, וכל פעם שתנסו לבדוק משהו לגביו הוא יחזיר מידע "חי" ומעודכן למצב הנוכחי של ה DOM.

במילים אחרות אנחנו יכולים להחליף את שורת ה querySelectorAll בפקודת children ולקבל את קובץ ה JavaScript הבא:

const ul = document.querySelector('ul');
const items = ul.children;

calcTotalItems();  

add.addEventListener('click', function() {
  const li = document.createElement('li');
  li.textContent = Math.floor(Math.random() * 100);
  ul.appendChild(li);
  calcTotalItems();  
});

clear.addEventListener('click', function() {
  ul.innerHTML = '';
  calcTotalItems();
});

function calcTotalItems() {
  total.textContent = items.length;
}

ולמרות שהפקודה children מופעלת מחוץ לפונקציה calcTotalItems, עדיין בכל פעם שפונים לשדה length של ה NodeList אנחנו מקבלים את הערך העדכני ולכן התוכנית עובדת בדיוק כמו בהתחלה.

למרות ששני אוביקטי ה NodeList נראים אותו דבר ממבט ראשון, ההתנהגות שלהם שונה מאוד והבדל זה יכול להשפיע גם על הביצועים בעת פיתוח יישומים. כשאתם כותבים קוד - שימו לב איזה NodeList אתם רוצים לקבל ועם איזה NodeList אתם עובדים.