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 הופכת לצומת אלמנט:
צומת טקסט - text node¶
הטקסט שבתוך האלמנטים הוא צומת נפרד:
כאן יש צומת אלמנט p ובתוכו צומת טקסט "Hello World".
צומת הערה - comment node¶
גם הערות HTML הופכות לצמתים בעץ:
צומת מסמך - 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¶
חשוב להבין איך כל החלקים מתחברים:
- HTML - מגדיר את המבנה של הדף (מה יש בדף)
- CSS - מגדיר את העיצוב של הדף (איך הדברים נראים)
- הדפדפן קורא את ה-HTML ובונה את ה-DOM (עץ האלמנטים)
- הדפדפן קורא את ה-CSS ובונה את ה-CSSOM (עץ הסגנונות)
- הדפדפן משלב את ה-DOM וה-CSSOM ויוצר את ה-Render Tree (עץ הרינדור)
- הדפדפן מצייר את הדף על המסך
- 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 באתר כלשהו ונסו:
תראו שהרקע של הדף השתנה מיידית. זה כי שינינו את ה-DOM, והדפדפן מגיב לשינוי.
ה-DOM הוא לא ה-HTML¶
זה נקודה חשובה שצריך להבין: ה-DOM הוא לא העתק של ה-HTML. הוא ייצוג חי שיכול להשתנות.
מצב 1 - הדפדפן מתקן שגיאות¶
גם אם ה-HTML שלנו לא תקין, הדפדפן ינסה לתקן ולבנות DOM תקין:
ה-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 המקורי