الوحدة 4 · JavaScript في المتصفّح
الـ DOM: التحديد والإنشاء والتحديث
18 دقراءة 3 دبيئة تجريب الكود
الدرس في لمحة
تفهم الـ DOM على أنه شجرة الصفحة الحيّة في المتصفّح، وتحدّد العناصر بمحدّدات CSS، وتغيّر نصوصها وأصنافها وسماتها، وتعرض قائمة انطلاقًا من البيانات.
لا فيديو هنا، بل الشيء الحقيقي: درس مكتوب وأمثلة قابلة للتشغيل واختبار وتدريب.
كل ما فعله Pocket حتى الآن حدث في الـ console. لكن المستخدم الحقيقي لا يفتح الـ console أبدًا؛ إنه يرى صفحة. وباقي هذا القسم ينقل Pocket إلى تلك الصفحة، وأول خطوة أن تتعلّم كيف ترى JavaScript صفحة الويب أصلًا.
الـ DOM: الصفحة على شكل كائنات
حين يحمّل المتصفّح HTML، لا يحتفظ به نصًا. بل يبني شجرة من الكائنات، كائنًا لكل عنصر، ويرسم الصفحة من تلك الشجرة. هذه الشجرة هي الـ DOM (نموذج كائنات المستند، Document Object Model)، والكائن العام document هو جذرها.
<body>
<h1>Pocket</h1>
<p id="total">Total: $0.00</p>
<ul id="expense-list"></ul>
<script src="app.js" defer></script>
</body>الـ DOM حيّ. غيّر كائنًا في الشجرة فيعيد المتصفّح رسم ذلك الجزء من الصفحة على الفور. وهذا كل ما يعنيه “تحديث الصفحة”.
السمة defer تخبر المتصفّح أن يشغّل app.js بعد أن يبني الشجرة كلها. ودونها، يُنفَّذ السكربت الموجود في <head> قبل أن يوجد الـ body، فلا يجد أي بحث شيئًا. أما السكربتات المحمّلة بـ type="module"، التي ستتعرّف عليها في القسم 5، فتُؤجَّل تلقائيًا.
تحديد العناصر
document.querySelector(selector) تأخذ محدّد CSS، أي الصياغة نفسها التي تكتبها في ملف الأنماط، وتُرجع أول عنصر مطابق. وquerySelectorAll تُرجع كل العناصر المطابقة.
const totalEl = document.querySelector("#total"); // by id
const list = document.querySelector("#expense-list");
const firstItem = document.querySelector("#expense-list li");
const allItems = document.querySelectorAll("li.big"); // every matchquerySelectorAll تُرجع NodeList. لها forEach وتعمل مع for...of؛ وانشرها، [...allItems]، حين تريد دوال المصفوفات مثل map وfilter.
حين لا يطابق شيء، تُرجع querySelector القيمة null، فيرمي السطر التالي الذي يستخدم النتيجة TypeError: Cannot set properties of null. وهذا الخطأ يعني في الغالب خطأً إملائيًا في المحدّد أو سكربتًا نُفّذ مبكرًا جدًا.
تغيير ما هو موجود
حين تحصل على عنصر، تصبح خصائصه أدوات التحكّم:
totalEl.textContent = "Total: $30.20"; // replace the text
totalEl.classList.add("over-budget"); // add a CSS class
totalEl.classList.toggle("highlight"); // add if absent, remove if present
totalEl.hidden = false; // show or hide
list.dataset.month = "2026-10"; // sets data-month="2026-10"textContent تضبط نصًا عاديًا. وفضّل الأصناف (classes) على ضبط style مباشرة: فملف CSS يقرّر شكل .over-budget، وJavaScript تقرّر فقط هل يحمل العنصر هذا الصنف. وعندها يستطيع المصمّمون تغيير الشكل دون أن يمسّوا منطقك.
إنشاء العناصر وعرض قائمة
لتضيف أشياء جديدة إلى الصفحة، أنشئ العناصر، واملأها، ثم ألحقها بالصفحة:
const li = document.createElement("li");
li.textContent = "Coffee: $4.50";
li.dataset.id = "exp-1";
list.append(li);createElement تصنع عنصرًا ليس على الصفحة بعد. وappend تُلحقه ابنًا أخيرًا لـ list، وعندها فقط يظهر. أما element.remove() فتُزيل عنصرًا من الصفحة.
لدى Pocket مصفوفة مصاريف ويحتاج إلى <li> لكل مصروف. وأنظف طريقة لإبقاء الصفحة والبيانات متّفقتين هي دالة render (دالة العرض): تُفرغ الحاوية، ثم تعيد بناءها من البيانات، في كل مرة تتغيّر فيها البيانات.
function renderExpenses(expenses) {
list.replaceChildren(); // remove the old items
for (const expense of expenses) {
const li = document.createElement("li");
li.textContent = `${expense.label}: ${formatMoney(expense.amount)}`;
li.dataset.id = expense.id;
if (expense.amount >= 10000) {
li.classList.add("big");
}
list.append(li);
}
const total = expenses.reduce((sum, e) => sum + e.amount, 0);
totalEl.textContent = `Total: ${formatMoney(total)}`;
}إعادة بناء كل شيء تبدو هدرًا، وهي كذلك مع آلاف العناصر. لكن مع عشرات العناصر في تطبيق شخصي، فهي سريعة، والأهم أنها بسيطة: هناك دالة واحدة فقط تقرّر شكل القائمة، ولا يمكن للصفحة أن تخرج عن تزامنها مع البيانات. وهذه الفكرة، أن الشاشة دالةٌ في البيانات، هي جوهر أطر العمل مثل React، التي تفعل الشيء نفسه بذكاء أكبر.
والقاعدة التي تجعل هذا ينجح هي الانضباط في الاتجاه. البيانات تسير في اتجاه واحد: غيّر المصفوفة، ثم استدعِ renderExpenses. لا تُصلح عنصر <li> واحدًا يدويًا “هذه المرة فقط”، ولا تقرأ المصاريف من الصفحة لتحسب شيئًا؛ فالصفحة صورة للبيانات، لا البيانات نفسها. وفي اللحظة التي يصبح فيها مكانان قادرين على تغيير ما على الشاشة، سيختلفان حتمًا في يوم ما.
صارت الصفحة تعرض بيانات Pocket، لكنها لا تستطيع التفاعل مع المستخدم بعد. الدرس التالي: الأحداث.
أهم النقاط
- يحوّل المتصفّح كود HTML إلى الـ DOM، وهو شجرة من الكائنات؛ وتغيير هذه الكائنات بـ JavaScript يغيّر الصفحة فورًا.
querySelectorتُرجع أول عنصر يطابق محدّد CSS، أوnull؛ وquerySelectorAllتُرجع كل العناصر المطابقة.- اضبط النص باستخدام
textContentلاinnerHTMLكلما جاء المحتوى من البيانات أو من المستخدمين، كي لا يُنفَّذ أبدًا على أنه HTML. - غيّر المظهر عبر
classList، واترك لـ CSS أن تقرّر شكل هذه الأصناف. - دالة
renderتُفرغ الحاوية وتعيد بناءها من بياناتك تُبقي الصفحة والبيانات متزامنتين.