404 Not Found

404 Not Found


nginx

مقدمة في DOM

DOM هو الجسر الوحيد بين JavaScript وصفحات الويب. بدون DOM، لن تكون JS أكثر من لغة حساب خالصة. مع DOM، يمكن لـ JS "رؤية" الصفحات، وتعديلها، وإحياؤها.

📖 ملخص

ما هو DOM؟

DOM يعني نموذج كائن المستند (Document Object Model). عندما يحمّل المتصفح HTML، لا يخزنه كنص عادي — يترجمه إلى شجرة كائنات. كل وسم HTML يصبح كائن (يسمى عقدة)، والعلاقات المتداخلة بين الأوسام تصبح علاقات أبو-ابن في الشجرة.

فكّر في DOM كشجرة مقلوبة: في الأعلى تماماً الجذر (document)، يتفرع إلى أغصان (html، head، body)، ثم إلى أوراق (div، p، span...).

هيكل شجرة DOM

شجرة DOM لصفحة HTML بسيطة تبدو تقريباً هكذا:

document
  └── html
       ├── head
       │    ├── meta
       │    └── title
       └── body
            ├── h1
            ├── p
            └── div
                 └── span

أنواع العقد

هناك ثلاثة أنواع أساسية من العقد في DOM:

نوع العقد الوصف قيمة nodeType
عقدة عنصر وسم HTML، مثل p، div 1
عقدة نص محتوى النص داخل الوسم 3
عقدة سمة سمة الوسم، مثل class، id 2 (مهمل للاستخدام العام)
HTML
<p class="intro">مرحباً بالعالم</p>

في هذا السطر الواحد: <p> هو عقدة عنصر، class="intro" هو عقدة سمة، و مرحباً بالعالم هو عقدة نص. في التطوير اليومي، عقد العناصر هي ما ستعمل معه أكثر.

كائن document

document هو نقطة الدخول إلى DOM — المتصفح يوفر هذا الكائن العام تلقائياً. من خلاله يمكنك:

ماذا يفعل DOM

JS نفسها لا يمكنها "رؤية" صفحة الويب — يمكنها فقط الحساب. DOM يوفر "العيون" و"الأيدي":

بدون DOM، ستكون JS و HTML شيئين منفصلين تماماً. DOM يربطهما.


مثال: عرض هيكل DOM

HTML
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
  <meta charset="UTF-8">
  <title>هيكل DOM</title>
  <style>
    body { font-family: sans-serif; padding: 20px; }
    .tree { margin: 16px 0; padding: 16px; background: #f5f5f5; border-radius: 8px; font-family: monospace; font-size: 14px; line-height: 1.8; direction: ltr; text-align: left; }
    .node { color: #4a90d9; font-weight: bold; }
    .text-node { color: #5cb85c; font-style: italic; }
    .attr-node { color: #f0ad4e; }
    .indent { margin-right: 24px; }
    .info { background: #e8f4fd; padding: 12px; border-radius: 6px; margin: 16px 0; }
  </style>
</head>
<body>
  <h1 id="mainTitle">عرض هيكل DOM</h1>
  <p class="intro">هذه فقرة</p>
  <div id="container">
    <span>عنصر سطر</span>
  </div>
  <div id="output"></div>
  <script>
    function buildTree(node, depth) {
      if (depth > 4) return "";
      var indent = "";
      for (var i = 0; i < depth; i++) indent += "  ";
      var result = "";

      if (node.nodeType === 1) {
        result += indent + '<span class="node">&lt;' + node.nodeName.toLowerCase();
        if (node.attributes && node.attributes.length > 0) {
          for (var a = 0; a < node.attributes.length && a < 3; a++) {
            result += ' <span class="attr-node">' +
              node.attributes[a].name + '="' + node.attributes[a].value + '"</span>';
          }
        }
        result += '&gt;</span>\n';
      } else if (node.nodeType === 3) {
        var text = node.textContent.trim();
        if (text) {
          result += indent + '<span class="text-node">"' + text + '"</span>\n';
        }
        return result;
      }

      var children = node.childNodes;
      for (var c = 0; c < children.length; c++) {
        result += buildTree(children[c], depth + 1);
      }

      if (node.nodeType === 1) {
        result += indent + '<span class="node">&lt;/' +
          node.nodeName.toLowerCase() + '&gt;</span>\n';
      }

      return result;
    }

    var treeStr = buildTree(document.documentElement, 0);

    document.getElementById("output").innerHTML = `
      <h2>شجرة DOM لهذه الصفحة</h2>
      <div class="tree"><pre>${treeStr}</pre></div>
      <div class="info">
        <strong>دليل الألوان:</strong>
        <span class="node">أزرق = عقدة عنصر</span> |
        <span class="text-node">أخضر = عقدة نص</span> |
        <span class="attr-node">برتقالي = سمة</span>
      </div>
    `;
  </script>
</body>
</html>
▶ جرّب الكود

مثال: استكشاف كائن document

HTML
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
  <meta charset="UTF-8">
  <title>كائن document</title>
  <style>
    body { font-family: sans-serif; padding: 20px; }
    .card { border: 2px solid #4a90d9; border-radius: 8px; padding: 16px; margin: 12px 0; background: #f0f7ff; }
    .card h3 { margin: 0 0 8px; color: #4a90d9; }
    .prop { display: flex; justify-content: space-between; padding: 6px 0; border-bottom: 1px solid #e0e0e0; }
    .prop:last-child { border-bottom: none; }
    .key { font-weight: bold; color: #333; }
    .val { color: #4a90d9; font-family: monospace; word-break: break-all; direction: ltr; }
    button { padding: 10px 20px; font-size: 16px; border: none; border-radius: 6px; cursor: pointer; margin: 4px; }
    .btn-blue { background: #4a90d9; color: #fff; }
    .btn-blue:hover { background: #357abd; }
    .btn-green { background: #5cb85c; color: #fff; }
    .btn-green:hover { background: #449d44; }
    .btn-orange { background: #f0ad4e; color: #fff; }
    .btn-orange:hover { background: #ec971f; }
    .demo-area { min-height: 60px; border: 2px dashed #ccc; border-radius: 8px; padding: 16px; margin: 12px 0; text-align: center; }
  </style>
</head>
<body>
  <h2>استكشاف كائن document</h2>

  <div class="card">
    <h3>خصائص document الشائعة</h3>
    <div id="props"></div>
  </div>

  <div class="card">
    <h3>جرّب بنفسك</h3>
    <button class="btn-blue" id="btnTitle">غيّر document.title</button>
    <button class="btn-green" id="btnBody">غيّر خلفية body</button>
    <button class="btn-orange" id="btnCreate">أنشئ عنصراً جديداً</button>
    <button class="btn-blue" id="btnReset" style="background:#888;">إعادة تعيين</button>
  </div>

  <div class="demo-area" id="demoArea">
    <p>هذه منطقة العرض</p>
  </div>

  <script>
    function showProps() {
      var props = [
        ["document.title", document.title],
        ["document.URL", document.URL],
        ["document.domain", document.domain],
        ["document.contentType", document.contentType],
        ["document.documentElement", document.documentElement.nodeName],
        ["document.body", document.body.nodeName],
        ["document.head", document.head.nodeName],
        ["document.body.childElementCount", document.body.childElementCount],
        ["document.all.length", document.all.length + " عنصر"]
      ];

      document.getElementById("props").innerHTML = props.map(function(p) {
        return '<div class="prop"><span class="key">' + p[0] +
          '</span><span class="val">' + p[1] + '</span></div>';
      }).join("");
    }

    showProps();

    var titleCount = 0;
    document.getElementById("btnTitle").addEventListener("click", function() {
      titleCount++;
      document.title = "تم التغيير " + titleCount + " مرة!";
      showProps();
    });

    var bgColors = ["#fff8e1", "#e8f5e9", "#fce4ec", "#e3f2fd", "#f3e5f5"];
    var bgIndex = 0;
    document.getElementById("btnBody").addEventListener("click", function() {
      document.body.style.background = bgColors[bgIndex % bgColors.length];
      bgIndex++;
    });

    var createCount = 0;
    document.getElementById("btnCreate").addEventListener("click", function() {
      createCount++;
      var p = document.createElement("p");
      p.textContent = "فقرة جديدة #" + createCount;
      p.style.color = "#4a90d9";
      p.style.fontWeight = "bold";
      document.getElementById("demoArea").appendChild(p);
      showProps();
    });

    document.getElementById("btnReset").addEventListener("click", function() {
      document.title = "كائن document";
      document.body.style.background = "";
      document.getElementById("demoArea").innerHTML = "<p>هذه منطقة العرض</p>";
      titleCount = 0;
      bgIndex = 0;
      createCount = 0;
      showProps();
    });
  </script>
</body>
</html>
▶ جرّب الكود

مثال: تحديد أنواع العقد

HTML
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
  <meta charset="UTF-8">
  <title>أنواع العقد</title>
  <style>
    body { font-family: sans-serif; padding: 20px; }
    table { border-collapse: collapse; margin: 16px 0; width: 100%; max-width: 700px; direction: ltr; }
    td, th { border: 1px solid #ddd; padding: 10px 14px; text-align: left; }
    th { background: #4a90d9; color: #fff; }
    .elem { background: #e3f2fd; }
    .text { background: #e8f5e9; }
    .note { background: #fff8e1; padding: 12px; border-radius: 6px; border-right: 4px solid #f0ad4e; margin: 16px 0; max-width: 700px; }
  </style>
</head>
<body>
  <h2>شرح أنواع العقد</h2>
  <p id="demo">هذه فقرة <span>تحتوي</span> على عقد متعددة</p>
  <div id="output"></div>
  <script>
    var demo = document.getElementById("demo");
    var results = [];

    function analyzeNode(node, depth) {
      var indent = "";
      for (var i = 0; i < depth; i++) indent += "│ ";
      var typeMap = { 1: "عقدة عنصر", 3: "عقدة نص" };
      var typeName = typeMap[node.nodeType] || "أخرى (" + node.nodeType + ")";
      var detail = "";

      if (node.nodeType === 1) {
        detail = "&lt;" + node.nodeName.toLowerCase() + "&gt;";
      } else if (node.nodeType === 3) {
        var text = node.textContent.trim();
        if (text) detail = '"' + text + '"';
        else return;
      }

      var cls = node.nodeType === 1 ? "elem" : "text";
      results.push(
        '<tr class="' + cls + '">' +
        "<td>" + indent + "</td>" +
        "<td><code>" + detail + "</code></td>" +
        "<td>" + typeName + "</td>" +
        "<td>" + node.nodeType + "</td>" +
        "</tr>"
      );

      var children = node.childNodes;
      for (var c = 0; c < children.length; c++) {
        analyzeNode(children[c], depth + 1);
      }
    }

    analyzeNode(demo, 0);

    document.getElementById("output").innerHTML = `
      <p>تحليل <code>&lt;p id="demo"&gt;هذه فقرة &lt;span&gt;تحتوي&lt;/span&gt; على عقد متعددة&lt;/p&gt;</code></p>
      <table>
        <tr><th>المستوى</th><th>المحتوى</th><th>نوع العقدة</th><th>nodeType</th></tr>
        ${results.join("")}
      </table>
      <div class="note">
        💡 ملاحظة: فواصل الأسطر والمسافات البيضاء بين الأوسام هي أيضاً عقد نصية! عملياً، <code>children</code> (عقد العناصر فقط) أكثر فائدة من
        <code>childNodes</code> (جميع العقد بما فيها النصية).
      </div>
    `;
  </script>
</body>
</html>
▶ جرّب الكود

❓ أسئلة شائعة

س هل DOM جزء من JavaScript؟
ج لا. DOM هو معيار يحدده W3C. JavaScript فقط يوفر APIs للتفاعل معه. Node.js ليس لديها DOM لأنها ليست بيئة متصفح. الخلط بين DOM و JS هو سوء فهم شائع للمبتدئين.
س لماذا هناك عقد نصية فارغة في childNodes؟
ج فواصل الأسطر والمسافات في HTML تُحلل كعقد نصية في DOM. مثلاً، المسافة البيضاء بين وسمين تصبح عقدة نصية (تحتوي على حرف سطر جديد). إذا أردت فقط عقد العناصر، استخدم children بدلاً من childNodes.
س هل تعديل DOM يؤثر على أداء الصفحة؟
ج نعم. كل تعديل DOM يمكن أن يسبب إعادة تدفق وإعادة رسم للمتصفح. التكرار الشديد ل DOM هو قاتل الأداء. أفضل الممارسات: ابنِ جميع تغييراتك في الذاكرة أولاً، ثم اكتبها إلى DOM دفعة واحدة.

📝 تمارين

  1. افتح أدوات المتصفح (F12)، اكتب console.dir(document) في Console، واستكشف خصائص وطرق كائن document. اذكر 5 تعرفها أو تجدها مثيرة.
  2. اكتب كوداً يحصل على عنصر body للصفحة الحالية، يطبع childElementCount (عدد عناصر الأبناء)، ثم يستخدم children للمرور على جميع عناصر الأبناء ويطبع اسم وسم كل منها.
  3. اشرح الفرق بين "عقد العناصر" و"عقد النص". بالنظر إلى <div>مرحباً <b>بالعالم</b></div>، ارسم هيكل شجرة العقد وحدد نوع كل عقدة.
100%