לדלג לתוכן

3.1 מה זה ה DOM הרצאה

מה זה ה-DOM

עד עכשיו למדנו HTML, CSS ו-JavaScript. עכשיו הגיע הזמן לחבר את הכל ביחד. כשאנחנו כותבים HTML ופותחים אותו בדפדפן, הדפדפן לא עובד ישירות עם הטקסט שכתבנו. הוא בונה מתוך ה-HTML מבנה נתונים שנקרא DOM - ראשי תיבות של Document Object Model.

  • ה-DOM הוא ייצוג חי של הדף בזיכרון של הדפדפן
  • הוא מאפשר ל-JavaScript לגשת לכל אלמנט בדף, לשנות אותו, למחוק אותו או להוסיף אלמנטים חדשים
  • בלי ה-DOM, לא היינו יכולים ליצור אתרים אינטראקטיביים
  • כל מה שאנחנו רואים בדף - הדפדפן בנה את זה מה-DOM

מ-HTML ל-DOM

כשהדפדפן מקבל קובץ HTML, הוא עובר תהליך שנקרא parsing - ניתוח הטקסט והפיכתו למבנה עץ:

<!DOCTYPE html>
<html>
<head>
    <title>My Page</title>
</head>
<body>
    <h1>Hello</h1>
    <p>Welcome to my page</p>
</body>
</html>

הדפדפן לוקח את ה-HTML הזה ובונה ממנו עץ שנראה ככה:

document
  └── html
       ├── head
       │    └── title
       │         └── "My Page" (text node)
       └── body
            ├── h1
            │    └── "Hello" (text node)
            └── p
                 └── "Welcome to my page" (text node)
  • כל תגית HTML הופכת ל-node (צומת) בעץ
  • יש קשרים של הורה-ילד בין האלמנטים, בדיוק כמו שהם מקוננים ב-HTML
  • זה עץ - יש שורש אחד (document) וממנו מסתעפים כל האלמנטים

סוגי צמתים - node types

לא כל הצמתים בעץ הם אלמנטים. יש כמה סוגים:

צומת אלמנט - element node

זה הסוג הכי נפוץ. כל תגית HTML הופכת לצומת אלמנט:

<div>, <p>, <h1>, <span>, <a>

צומת טקסט - text node

הטקסט שבתוך האלמנטים הוא צומת נפרד:

<p>Hello World</p>

כאן יש צומת אלמנט p ובתוכו צומת טקסט "Hello World".

צומת הערה - comment node

גם הערות HTML הופכות לצמתים בעץ:

<!-- this is a comment -->

צומת מסמך - document node

זה השורש של כל העץ. הוא לא מייצג תגית HTML, אלא את המסמך עצמו.

אובייקט ה-document

ה-document הוא נקודת הכניסה שלנו ל-DOM. דרכו אנחנו ניגשים לכל מה שיש בדף:

// the document object is the entry point to the DOM
console.log(document);

// access the entire HTML element
console.log(document.documentElement);

// access the head
console.log(document.head);

// access the body
console.log(document.body);

// access the page title
console.log(document.title);

// change the page title
document.title = "New Title";
  • ה-document הוא אובייקט גלובלי שזמין תמיד ב-JavaScript בדפדפן
  • הוא מכיל מתודות לחיפוש, יצירה ומניפולציה של אלמנטים
  • נשתמש בו כל הזמן כשנעבוד עם ה-DOM

אובייקט ה-window

ה-window הוא האובייקט הגלובלי בדפדפן. הוא מייצג את חלון הדפדפן עצמו:

// window is the global object in the browser
console.log(window);

// get the window dimensions
console.log(window.innerWidth);
console.log(window.innerHeight);

// scroll the page
window.scrollTo(0, 500);

// show alerts and prompts
window.alert("Hello!");
window.confirm("Are you sure?");
window.prompt("What is your name?");

// open a new tab
window.open("https://google.com");
  • ה-window מכיל את כל מה שזמין בצורה גלובלית בדפדפן
  • ה-document הוא בעצם window.document
  • כשאנחנו כותבים console.log זה בעצם window.console.log
  • כל משתנה גלובלי שאנחנו יוצרים עם var נשמר על ה-window
  • let ו-const לא נשמרים על ה-window (זו אחת הסיבות להעדיף אותם)
var myGlobalVar = "hello";
console.log(window.myGlobalVar); // "hello"

let myLetVar = "world";
console.log(window.myLetVar); // undefined

הקשר בין HTML, CSS, JS ו-DOM

חשוב להבין איך כל החלקים מתחברים:

  1. HTML - מגדיר את המבנה של הדף (מה יש בדף)
  2. CSS - מגדיר את העיצוב של הדף (איך הדברים נראים)
  3. הדפדפן קורא את ה-HTML ובונה את ה-DOM (עץ האלמנטים)
  4. הדפדפן קורא את ה-CSS ובונה את ה-CSSOM (עץ הסגנונות)
  5. הדפדפן משלב את ה-DOM וה-CSSOM ויוצר את ה-Render Tree (עץ הרינדור)
  6. הדפדפן מצייר את הדף על המסך
  7. JavaScript יכול לשנות את ה-DOM ואת ה-CSSOM, מה שגורם לדפדפן לעדכן את מה שמוצג
// JS can change the DOM structure
document.body.innerHTML = "<h1>I replaced everything!</h1>";

// JS can change styles
document.body.style.backgroundColor = "lightblue";

כלי הפיתוח - DevTools Elements

לשונית ה-Elements ב-DevTools מציגה את ה-DOM (לא את קוד המקור של ה-HTML).

  • לחצו F12 או קליק ימני ואז Inspect כדי לפתוח את ה-DevTools
  • בלשונית Elements אתם רואים את עץ ה-DOM
  • אפשר ללחוץ על כל אלמנט ולראות את המאפיינים שלו
  • אפשר לערוך אלמנטים, למחוק אותם, לגרור אותם למקום אחר
  • שינויים ב-DevTools הם זמניים - ריענון הדף מחזיר הכל

דוגמה חשובה - פתחו את DevTools באתר כלשהו ונסו:

// type this in the Console tab
document.body.style.backgroundColor = "red";

תראו שהרקע של הדף השתנה מיידית. זה כי שינינו את ה-DOM, והדפדפן מגיב לשינוי.

ה-DOM הוא לא ה-HTML

זה נקודה חשובה שצריך להבין: ה-DOM הוא לא העתק של ה-HTML. הוא ייצוג חי שיכול להשתנות.

מצב 1 - הדפדפן מתקן שגיאות

גם אם ה-HTML שלנו לא תקין, הדפדפן ינסה לתקן ולבנות DOM תקין:

<!-- our broken HTML -->
<p>Hello
<p>World

ה-DOM שייבנה:

<!-- what the browser actually builds -->
<html>
<head></head>
<body>
    <p>Hello</p>
    <p>World</p>
</body>
</html>

הדפדפן הוסיף <html>, <head>, <body> וסגר את תגיות ה-<p>.

מצב 2 - JavaScript משנה את ה-DOM

<!DOCTYPE html>
<html>
<head><title>Demo</title></head>
<body>
    <div id="container">
        <p>Original content</p>
    </div>

    <script>
        // add a new element that doesn't exist in the HTML source
        const newParagraph = document.createElement("p");
        newParagraph.textContent = "I was added by JavaScript!";
        document.getElementById("container").appendChild(newParagraph);
    </script>
</body>
</html>
  • ה-HTML המקורי מכיל פסקה אחת בלבד
  • אחרי שה-JavaScript רץ, ה-DOM מכיל שתי פסקאות
  • אם תסתכלו ב-Elements ב-DevTools, תראו את שתי הפסקאות
  • אם תסתכלו ב-View Source (קליק ימני ואז View Page Source), תראו רק את הפסקה המקורית
  • זה ההבדל בין ה-HTML (קוד המקור) לבין ה-DOM (המצב החי)

מתי ה-DOM מוכן - DOMContentLoaded מול load

כשהדפדפן טוען דף, זה לא קורה ברגע אחד. יש שני אירועים חשובים:

אירוע DOMContentLoaded

נורה כשה-HTML נטען במלואו וה-DOM נבנה, לפני שתמונות וקבצים חיצוניים סיימו להיטען:

document.addEventListener("DOMContentLoaded", function() {
    console.log("DOM is ready! Images may still be loading...");
    // safe to access and manipulate DOM elements here
});

אירוע load

נורה כשהכל סיים להיטען - כולל תמונות, CSS, פונטים וכל משאב חיצוני:

window.addEventListener("load", function() {
    console.log("Everything is fully loaded!");
    // safe to access image dimensions, etc.
});
  • ברוב המקרים, DOMContentLoaded הוא מה שאנחנו צריכים
  • load שימושי כשאנחנו צריכים לדעת גדלים של תמונות, למשל

מיקום ה-script - סוף ה-body מול defer

יש בעיה: אם ה-JavaScript שלנו מנסה לגשת לאלמנט ב-DOM לפני שהאלמנט נוצר, נקבל שגיאה.

הבעיה

<!DOCTYPE html>
<html>
<head>
    <script>
        // this runs BEFORE the body is parsed!
        const title = document.getElementById("main-title");
        console.log(title); // null - the element doesn't exist yet!
    </script>
</head>
<body>
    <h1 id="main-title">Hello</h1>
</body>
</html>

ה-script רץ כשה-head נטען, אבל ה-body עדיין לא נבנה. לכן getElementById מחזיר null.

פתרון 1 - שים את ה-script בסוף ה-body

<!DOCTYPE html>
<html>
<head>
    <title>Demo</title>
</head>
<body>
    <h1 id="main-title">Hello</h1>
    <p>Some content</p>

    <!-- script at the end of body - DOM is already built -->
    <script>
        const title = document.getElementById("main-title");
        console.log(title); // works! the element exists
    </script>
</body>
</html>

פתרון 2 - שימוש ב-defer

<!DOCTYPE html>
<html>
<head>
    <title>Demo</title>
    <!-- defer: download now, execute after DOM is ready -->
    <script src="app.js" defer></script>
</head>
<body>
    <h1 id="main-title">Hello</h1>
    <p>Some content</p>
</body>
</html>
  • תכונת defer אומרת לדפדפן: "תוריד את הקובץ עכשיו, אבל תריץ אותו רק אחרי שה-DOM מוכן"
  • זה פתרון יותר מודרני ומומלץ
  • defer עובד רק עם קבצי JavaScript חיצוניים (עם src), לא עם inline scripts

פתרון 3 - שימוש ב-DOMContentLoaded

<!DOCTYPE html>
<html>
<head>
    <script>
        document.addEventListener("DOMContentLoaded", function() {
            // this code runs only after the DOM is ready
            const title = document.getElementById("main-title");
            console.log(title); // works!
        });
    </script>
</head>
<body>
    <h1 id="main-title">Hello</h1>
</body>
</html>

מה ההבדל בין defer לבין async

יש גם תכונה שנקראת async:

<!-- defer: download in parallel, execute after DOM is ready, keeps order -->
<script src="first.js" defer></script>
<script src="second.js" defer></script>

<!-- async: download in parallel, execute as soon as downloaded, no order guarantee -->
<script src="analytics.js" async></script>
  • defer - מוריד ברקע, מריץ אחרי שה-DOM מוכן, שומר על הסדר של הסקריפטים
  • async - מוריד ברקע, מריץ מיד כשההורדה נגמרה, לא שומר על סדר
  • async טוב לסקריפטים עצמאיים כמו אנליטיקס שלא תלויים ב-DOM
  • ברוב המקרים נשתמש ב-defer

דוגמה מסכמת

נשלב את כל מה שלמדנו בדוגמה אחת:

<!DOCTYPE html>
<html lang="he" dir="rtl">
<head>
    <meta charset="UTF-8">
    <title>DOM Demo</title>
    <script src="app.js" defer></script>
    <style>
        body { font-family: Arial, sans-serif; padding: 20px; }
        .highlight { background-color: yellow; padding: 5px; }
    </style>
</head>
<body>
    <h1 id="title">DOM Demo Page</h1>
    <p class="intro">This paragraph is in the original HTML.</p>
    <div id="container">
        <p>Content inside container.</p>
    </div>
    <!-- this comment is also a DOM node -->
</body>
</html>

קובץ app.js:

// this runs after the DOM is ready (thanks to defer)

// access elements
const title = document.getElementById("title");
const intro = document.querySelector(".intro");
const container = document.getElementById("container");

// log the document structure
console.log("Title:", title.textContent);
console.log("Intro:", intro.textContent);
console.log("Body children:", document.body.children.length);

// modify the DOM - this changes what the user sees
title.textContent = "Modified Title!";
title.style.color = "blue";

// add a new element that doesn't exist in the HTML source
const newElement = document.createElement("p");
newElement.textContent = "I was created by JavaScript!";
newElement.classList.add("highlight");
container.appendChild(newElement);

// log window info
console.log("Window width:", window.innerWidth);
console.log("Window height:", window.innerHeight);
console.log("Page title:", document.title);

  • ה-script משתמש ב-defer אז הוא רץ רק אחרי שה-DOM מוכן
  • הוא ניגש לאלמנטים, משנה אותם ומוסיף אלמנטים חדשים
  • אחרי שהקוד רץ, ה-DOM שונה מה-HTML המקורי
  • ב-DevTools Elements נראה את המצב החדש (עם השינויים)
  • ב-View Page Source נראה את ה-HTML המקורי (בלי השינויים)

סיכום

  • ה-DOM הוא מבנה עץ שהדפדפן בונה מה-HTML
  • ה-document הוא נקודת הכניסה לעץ - דרכו אנחנו ניגשים לאלמנטים
  • ה-window הוא האובייקט הגלובלי של הדפדפן
  • ה-DOM הוא לא ה-HTML - הוא ייצוג חי שיכול להשתנות
  • צריך לוודא שה-DOM מוכן לפני שניגשים לאלמנטים (defer, סוף body, או DOMContentLoaded)
  • כלי הפיתוח (DevTools) מציגים את ה-DOM, לא את ה-HTML המקורי