اذهب إلى المحتوى

استكشاف خاصيات عقد DOM بجافاسكربت: الصنف والوسم والمحتويات


محمد أمين بوقرة

لنتناول عُقدَ DOM بمزيدٍ من التعمّق. في هذا المقال، سننظر أكثر في ماهيّتها وسنتعرّف على أكثر خاصّيّاتها استخداما.

أصناف العقد في DOM

قد تختلف خاصّيات العقد باختلاف العقد نفسها. فعلى سبيل المثال، تملك العقد التي تمثّل الوسم <a> خاصّيّات تتعلّق بالروابط، وتملك العقد التي تمثّل الوسم <input> خاصّيّات تتعلّق بالمُدخلات، وهكذا. لكن بالرغم من اختلاف العقد عن بعضها، فإنّ لها أيضا خاصّيّات وتوابع مشتركة بينها، ﻷنّ جميع أصناف العقد في DOM تُشكّل تسلسلا هرميّا واحدا.

تنتمي كلّ عقدة من DOM إلى صنف معيّن من اﻷصناف المسبقة البناء (built-in class).

يكون جذر التسلسل الهرميّ هو الصنف EventTarget، الذي يرث عنه الصنفُ Node، الذي يرث عنه بدوره باقي العقد في DOM.

هذه صورة للتسلسل الهرمي، وسيتبعها المزيد من الشرح:

dom-class-hierarchy.png

تتمثّل اﻷصناف في:

  • EventTarget -- هو صنفٌ "مجرّدٌ" (abstract) يكون عند الجذر. لا تُنشأ كائنات من هذا الصنف أبدا. وهو بمثابة القاعدة التي تُمكّن جميع العُقد في DOM من دعم من يُسمّى باﻷحداث (التي سندرسها لاحقا).
  • Node -- هو أيضا صنفٌ "مجرّد" بمثابة القاعدة للعقد في DOM، ويوفّر الوظائف الشجريّة اﻷساسيّة مثل parentNode و nextSibling و childNodes وغيرها (التي تُسمّى جالبة - getters). لا تُنشأ كائنات من هذا الصنف، لكنّ هناك أصنافا حقيقيّة (concrete) من العُقد التي ترث هذا الصنف، منها Text للعقد النصّيّة و Element للعقد العنصريّة وأصنافٌ أخرى أغرب مثل Comment للعقد التعليقيّة.
  • Element -- هو الصنف الأساسيّ للعناصر في DOM. يتيح توابع للتنقّل بين العناصر مثل nextElementSibling و children وتوابع للبحث مثل getElementsByTagName و querySelector. بما أنّ المتصفّح لا يدعم فقط HTML بل XML و SVG كذلك، فإنّ الصنف Element يمثّل قاعدة لأصنافٍ أخصّ مثل SVGElement و XMLElement و HTMLElement.
  • HTMLElement -- هو الصنف اﻷساسيّ لجميع عناصر HTML ، ويرث عنه عناصرُ HTML الحقيقيّة:
  • HTMLInputElement -- هو صنف العناصر <input>.
  • HTMLBodyElement -- هو صنف العناصر <body>.
  • HTMLAnchorElement -- هو صنف العناصر <a>.
  • … إلى غير ذلك، فلكلّ وسمٍ صنفه الخاصّ الذي قد يتيح له خاصّيّات وتوابع تختصّ به.

إذًا فمجموع الخاصّيّات والتوابع التي لدى عقدةٍ ما هو ناتج عن الوراثة.

على سبيل المثال، لنأخذ كائن DOM لعنصر <input>. ينتمي هذا الكائن إلى صنف HTMLInputElement، ويكتسب خاصّياته وتوابعه من تراكم اﻷصناف التالية (مرتبة بحسب الوراثة):

  • HTMLInputElement -- يتيح هذا الصنف خاصّيّات تتعلّق بالمُدخلات.
  • HTMLElement -- يتيح توابع مشتركة بين عناصر HTML (وجوالب/ضوابط -- getters/setters).
  • Element -- يتيح توابع عامّة للعناصر.
  • Node -- يتيح خاصّيات مشتركة بين عقد DOM.
  • EventTarget -- يتيح الدعم للأحداث (سنتطرق لها لاحقا).
  • … وأخيرا هو يرث من Object ، مما يجعل توابع "الكائنات الصرفة" مثل hasOwnProperty متاحة له كذلك.

لمعرفة اسم صنف العقدة في DOM، يكفي تذكّر أنّ للكائنات عادةً خاصّيّة constructor. تشير هذه الخاصّيّة إلى الدالّة البانية (constructor) الخاصّة بالصنف، و constructor.name هو اسمها:

alert( document.body.constructor.name ); // HTMLBodyElement

… أو يكفي مجرّد تطبيق toString عليه:

alert( document.body ); // [object HTMLBodyElement]

يمكن كذلك استخدام instanceof للتحقّق من الوراثة:

alert( document.body instanceof HTMLBodyElement ); // true
alert( document.body instanceof HTMLElement ); // true
alert( document.body instanceof Element ); // true
alert( document.body instanceof Node ); // true
alert( document.body instanceof EventTarget ); // true

كما يمكن أن نلاحظ، تُعدّ عقد DOM كائنات جافاسكربت عاديّة، وهي تعتمد في التوريث على كائن prototype.

يمكن أيضا ملاحظة ذلك بسهولة من خلال عرض عنصرٍ ما بواسطة (console.dir(elem في المتصفّح. يمكنك ملاحظة كلٍّ من HTMLElement.prototype و Element.prototype وغيرها في الطرفية.


(console.dir(elem مقابل (console.log(elem

تدعم أدوات المطوّر في معظم المتصفحات اﻷمرين: console.log و console.dir. يعرض كلّ منهما معاملاته في الطرفيّة. بالنسبة لكائنات جافاسكربت فإنّ هذين اﻷمرين متماثلان في العادة.

لكن بالنسبة لعناصر DOM فهما مختلفان:

  • تعرض (console.log(elem شجرة DOM الخاصّة بالعنصر.
  • تعرض (console.dir(elem العنصر على شكل كائن DOM، وتساعد على استكشاف خاصيّاته.

جرّب ذلك على document.body



IDL في المواصفة

لا توصف أصناف DOM في المواصفة باستخدام جافاسكربت، لكن باستخدام لغة خاصة تُسمى لغة وصف الواجهة (Interface description language أو IDL باختصار)، وهي سهلة الفهم في العادة.

في IDL، تُسبق جميع الخاصّيّات بنوعها. فمثلا DOMString و boolean وهكذا.

إليك مقتطفا منها مع التعليقات:

// HTMLInputElement تعريف
// HTMLElement يرث من HTMLInputElement تعني النقطان الرئسيتان ":" أنّ   
interface HTMLInputElement: HTMLElement {
  //  <input> هنا توضع خاصّيّات وتوابع العنصر

  // أنّ قيمة الخاصّيّة هي عبارة عن سلسلة نصيّة "DOMString" تعني
  attribute DOMString accept;
  attribute DOMString alt;
  attribute DOMString autocomplete;
  attribute DOMString value;

  // (true/false) خاصّيّة ذات قيمةٍ بوليانية
  attribute boolean autofocus;
  ...
  // أن التابع لايعيد أيّة قيمة "void" وهنا مكان التابع: تعني
  void select();
  ...
}

خاصية "nodeType"

تقدّم خاصّيّة "nodeType" طريقة أخرى "قديمة الطراز" للحصول على "نوع" العقدة في DOM.

لهذه الخاصّيّة قيمة عددية:

  • elem.nodeType == 1 للعقد العنصريّة.
  • elem.nodeType == 3 للعقد النصّيّة.
  • elem.nodeType == 9 لكائن document.
  • هناك بعض القيم اﻷخرى في المواصفة.

على سبيل المثال:

<body>
  <script>
  let elem = document.body;

  // لنرى ما نوع هذه العقدة
  alert(elem.nodeType); // 1 => عنصر

  // ... وأوّل ابن لها هو
  alert(elem.firstChild.nodeType); // 3 => نصّ

  //  9 النوع هو ،document بالنسبة لكائن
  alert( document.nodeType ); // 9
  </script>
</body>

في السكربتات الحديثة، يمكننا استخدام instanceof وغيرها من الاختبارات المعتمدة على الصنف لمعرفة نوع العقدة، لكن أحيانا قد تكون nodeType أبسط. يمكننا فقط قراءة nodeType دون التعديل عليها.

الوسم: nodeName و tagName

إذا كانت لدينا عقدة من DOM، فيمكننا قراءة اسم وسمها من خلال خاصّيّات nodeName و tagName.

على سبيل المثال:

alert( document.body.nodeName ); // BODY
alert( document.body.tagName ); // BODY

هل هناك فرق بين tagName و nodeName؟

بالتأكيد، يظهر الفرق في اسميهما لكنّه في الحقيقة طفيف نوعا ما.

  • تكون خاصّيّة tagName للعقد العنصريّة فقط.
  • تكون خاصّية nodeName مُعرّفةً في جميع العقد:
  • بالنسبة للعناصر هي مماثلة لـ tagName.
  • بالنسبة ﻷنواع العقد اﻷخرى (النصوص والتعليقات وغيرها) فهي سلسلة نصّيّة تحمل نوع العقدة.

بعبارة أخرى، تُدعم tagName من العقد النصّيّة فقط (إذ أنّ منشأها من الصنف Element)، بينما تستطيع nodeName الإخبار عن أنواع العقد اﻷخرى.

على سبيل المثال، لنوازن بين tagName و nodeName بتطبيقهما على كائن document وعلى عقدة تعليقيّة:

<body><!-- comment -->

  <script>
    // بالنسبة للتعليق
    alert( document.body.firstChild.tagName ); // undefined (not an element) -- (أي غير معرّف (ليس عنصرا
    alert( document.body.firstChild.nodeName ); // #comment

    // document بالنسبة لـ
    alert( document.tagName ); // undefined (not an element)
    alert( document.nodeName ); // #document
  </script>
</body>

إذا اقتصرنا على التعامل مع العناصر فقط، فيمكننا استعمال كلٍّ من tagName و nodeName -- لا فرق بينهما.


يكون اسم الوسم دائما باﻷحرف الكبيرة (uppercase) ماعدا في وضع XML

للمتصفّح وضعان لمعالجة المستندات: HTML و XML. يُستخدم وضع HTML عادة مع صفحات الويب. يُفًّعل وضع XML عندما يستقبل المتصفّح مستند XML بالترويسة Content-Type: application/xml+xhtml.

في وضع HTML، تكون tagName و nodeName دائما باﻷحرف الكبيرة: فهي BODY سواء بالنسبة لـ <body> أو <BoDy>.

في وضع XML، تُترك اﻷحرف "كما هي". في الوقت الحاضر، يندر استخدام وضع XML.


innerHTML: المحتويات

تُمكّن innerHTML من تحصيل الـ HTML الذي بداخل العنصر على شكل سلسلة نصيّة. كما يمكننا أيضا التعديل عليه، مما يجعله من أقوى الطرق للتعديل على الصفحة.

هذا المثال يبيّن كيفيّة عرض محتويات document.body ثم استبدالها كليّة:

<body>
  <p>A paragraph</p>
  <div>A div</div>

  <script>
    alert( document.body.innerHTML ); // قراءة المحتويات الحاليّة
    document.body.innerHTML = 'The new BODY!'; // ثم استبدالها
  </script>

</body>

يمكننا محاولة إدخال HTML غير سليم، ليقوم المتصفّح بتصحيح الأخطاء:

<body>

  <script>
    document.body.innerHTML = '<b>test'; // نسينا إغلاق الوسم
    alert( document.body.innerHTML ); // <b>test</b> (صٌلّحت)
  </script>

</body>


لا تُنفّذ السكربتات

إذا أدرجت innerHTML سكربت <script> في المستند، فإنّه يصير جزءًا من HTML لكنّه لا يُنفّذ.


تعيد "=+innerHTML" الكتابة كليّة

يمكننا إضافة HTML إلى عنصر ما بواسطة "elem.innerHTML+="more html هكذا:

chatDiv.innerHTML += "<div>Hello<img src='smile.gif'/> !</div>";
chatDiv.innerHTML += "How goes?";

لكن يجب أن ننتبه عند فعل ذلك، فما يحدث فعلا ليست مجرّد إضافة، بل هي إعادة كتابة بالكليّة.

ظاهريّا، يفعل هذان السطران نفس الشيء:

elem.innerHTML += "...";
// :هو طريقة مختصرة لكتابة
elem.innerHTML = elem.innerHTML + "..."

بعبارة أخرى، ما تقوم به =+innerHTML هو التالي:

  • يُحذف المحتوى القديم.
  • يُكتب في مكانه innerHTML الجديد (الذي هو سلسلة متكوّنة من المحتوى القديم والجديد).

نتيجةً "لتصفير" المحتوى وإعادة كتابته من جديد، فإنّه يُعاد تحميل جميع الصّور وغيرها من الموارد.

في مثال chatDiv أعلاه، يعيد السطر ?"chatDiv.innerHTML+="How goes إنشاء محتوى HTML ثمّ يعيد تحميل الصّورة smile.gif (يُأمل أن تكون مخبّأةً في الذاكرة المؤقتة - Cache). إذا كان لدى chatDiv الكثير من النصوص والصور اﻷخرى، فإنّ إعادة التحميل تصير ظاهرة للعيان.

هناك آثار جانبية أخرى كذلك. على سبيل المثال، إذا كان النصّ الموجود قد حُدّد بواسطة الفأرة، فإنّ معظم المتصفّحات ستزيل التحديد عنه عند إعادة كتابة innerHTML . كما أنّه إذا كان هناك <input> فيها نصّ أدخله الزائر، فإنّ النصّ سيُزال أيضا. إلى غير ذلك.

لحسن الحظّ، هناك طرق أخرى لإضافة HTML عدا innerHTML ، سندرسها قريبا.

outerHTML: كامل HTML العنصر

تحتوي خاصّيّة outerHTML على كامل HTML العنصر. تشبه هذه الخاصّيّة innerHTML لكن بالإضافة إلى العنصر نفسه. إليك مثالا:

<div id="elem">Hello <b>World</b></div>

<script>
  alert(elem.outerHTML); // <div id="elem">Hello <b>World</b></div>
</script>

انتبه: على خلاف innerHTML، لا يؤدي التعديل على outerHTML إلى تعديل العنصر، بل سيؤدي ذلك إلى استبداله في DOM.

نعم، يبدو ذلك غريبا، وهو فعلا كذلك. لهذا خصصنا له تنبيها منفصلا هنا. ألقِ نظرة على هذا المثال:

<div>Hello, world!</div>

<script>
  let div = document.querySelector('div');

  //  <p>...</p> بـ div.outerHTML استبدال 
  div.outerHTML = '<p>A new element</p>'; // (*)

  // على حاله 'div'  ياللجعب! بقي 
  alert(div.outerHTML); // <div>Hello, world!</div> (**)
</script>

يبدو ذلك غريبا، صحيح؟

في السطر (*)، استبدلنا <div> بـ <p>A new element</p>. في المستند الخارجي (DOM)، يمكننا ملاحظة المحتوى الجديد بدل <div>. لكن كما يمكن الملاحظة في السطر (**)، لم تتغيّر قيمة المتغيّر القديم div!

لا يغيّر الإسنادُ outerHTML العنصرَ في DOM (في هذا المثال، الكائن الذي يشير إليه المتغير 'div')، لكنّه يحذفه من DOM ويدرج مكانه الـ HTML الجديد.

فالذي حصل بفعل ...=div.outerHTML هو ما يلي:

  • حُذف div من المستند.
  • أُدرج في مكانه قطعة HTML أخرى <p>A new element</p> .
  • لا يزال لـ div نفس القيمة. ولم يُحفظ HTML الجديد في أيّ متغيّر.

من السهل جدّا ارتكاب خطأ هنا: التعديل على div.outerHTML ثمّ مواصلة العمل بـ div وكأنّ فيه المحتوى الجديد. لكنّه ليس كذلك. ينطبق هذا على innerHTML لكن ليس على outerHTML.

بإمكاننا التعديل على elem.outerHTML، لكن ينبغي التنبّه إلى أنّ ذلك لا يغيّر العنصر 'elem'، بل يضع مكانه HTML الجديد. يمكننا الحصول على مراجع للعناصر الجديدة من خلال البحث في DOM.

nodeValue/data: محتوى العقد النصية

لا تصلح الخاصّيّة innerHTML إلّا مع العقد العنصريّة.

بالنسبة للعقد الأخرى، كالعقد النصّيّة، يقابل ذلك خاصّيّات nodeValue و data. هاتان الخاصّيّتان متماثلتان تقريبا في التطبيق، مع بعض الفروق الطفيفة في المواصفة. لذا سنستعمل data ﻷنّها أقصر.

هذا مثالٌ لقراءة محتوى عقدة نصّيّة وتعليق:

<body>
  Hello
  <!-- Comment -->
  <script>
    let text = document.body.firstChild;
    alert(text.data); // Hello

    let comment = text.nextSibling;
    alert(comment.data); // Comment
  </script>
</body>

يمكننا تصوّر دافعٍ لقراءة وتعديل العقد النصّيّة، لكن لماذا التعليقات؟

يُضمّن فيها المطوّرون أحيانا معلومات أو تعليمات متعلّقة بالقالب في HTML، على هذا النحو:

<!-- if isAdmin -->
  <div>Welcome, Admin!</div>
<!-- /if -->

… فيمكن لجافاسكربت قراءتها من خلال خاصّيّة data ثمّ معالجة التعليمات التي تتضمّنها.

textContent: نص محض

تتيح خاصّية textContent الوصول إلى النصّ الذي بداخل العنصر: مجرّد النصّ، دون أيّة وسوم.

على سبيل المثال:

<div id="news">
  <h1>Headline!</h1>
  <p>Martians attack people!</p>
</div>

<script>
  // Headline! Martians attack people!
  alert(news.textContent);
</script>

كما يمكن أن نلاحظ، أُعيد النصّ فقط، وكأنّ جميع الـوسوم قُطعت، وبقي النصّ الذي بداخلها.

عمليّا، يندر أن يُحتاج إلى قراءة مثل هذا النصّ.

يعدّ التعديل على textContent مفيدا أكثر بكثير، لأنّه يمكّن من كتابة النصوص على "النحو الآمن".

لنتفرض أن لدينا سلسلة نصّيّة ما، أدخلها مستخدمٌ مثلا، ونريد أن نعرضها.

  • باستخدام innerHTML سندرجها "على شكل HTML"، بجميع وسوم HTML.
  • باستخدام textContent سندرجها "على شكل نصّ"، وتُعامل جميع الرموز معاملة حرفيّة.

قارن بين الإثنين:

<div id="elem1"></div>
<div id="elem2"></div>

<script>
  let name = prompt("What's your name?", "<b>Winnie-the-pooh!</b>");

  elem1.innerHTML = name;
  elem2.textContent = name;
</script>

  1. يحصُل <div> اﻷوّل على الاسم (name) "على شكل HTML": جميع الوسوم تُعامل كوسوم، ونرى بذلك الاسم بخطّ عريض (bold).
  2. يحصُل <div> الثاني على الاسم "على شكل نصّ"، فنرى حرفيًّا <b>Winnie-the-pooh!</b>.

في أغلب الحالات، نتوقّع من المستخدم أن يدخل نصًّا، ونريد أن نعامله كنصّ. لا نريد HTML غير متوقّع في موقعنا. وهذا ما يفعله الإسناد إلى textContent بالضبط.

خاصية 'hidden'

تُحدّد السّمة "hidden" وخاصيّة DOM التي تحمل نفس الاسم ما إذا كان العنصر مرئيّا أو لا.

بإمكاننا استخدامها في HTML أو إسنادها في جافاسكربت كالتالي:

<div>Both divs below are hidden</div>

<div hidden>With the attribute "hidden"</div>

<div id="elem">JavaScript assigned the property "hidden"</div>

<script>
  elem.hidden = true;
</script>

مبدئيّا، تعمل hidden تماما مثل "style="display:none ، لكنّها أقصر في الكتابة.

إليك عنصرا وامضًا:

<div id="elem">A blinking element</div>

<script>
  setInterval(() => elem.hidden = !elem.hidden, 1000);
</script>

المزيد من الخاصيات

للعناصر في DOM المزيد من الخاصّيّات، خصوصا تلك التي تتعلق باﻷصناف:

  • value -- القيمة التي في <input> و <select> و <textarea> (المتعلقة بـ HTMLInputElement و HTMLSelectElement …).
  • href -- قيمة "href" في <"..."=a href> (المتعلقة بـ HTMLAnchorElement).
  • id -- قيمة السمة "id" لجميع العناصر (المتعلقة بـHTMLElement).
  • … والمزيد من ذلك…

على سبيل المثال:

<input type="text" id="elem" value="value">

<script>
  alert(elem.type); // "text"
  alert(elem.id); // "elem"
  alert(elem.value); // value
</script>

تقابل معظم السمات القياسية في HTML خاصّيات في DOM، ويمكننا الوصول إليها بهذه الطريقة.

إذا أردنا معرفة جميع الخاصّيّات المدعومة في صنف معيّن، يمكننا الاطلاع عليها في المواصفة. على سبيل المثال، HTMLInputElement موثّقة هنا.

أمّا إذا أردنا الحصول عليها بسرعة، أو كنّا مهتمين بمواصفة ملموسة في المتصفّح، يمكننا استعراض العنصر بواسطة (console.dir(elem لقراءة الخاصّيّات. أو يمكن كذلك استكشاف "خاصّيّات DOM" في لسان العناصر (Elements) في أدوات المطوّر في المتصفّح.

الملخص

تنتمي كلّ عقدة في DOM إلى صنف معيّن. تشكّل اﻷصناف تسلسلا هرميّا. يكون مجموع الخاصّيّات والتوابع التي لدى عقدة ما ناتجا عن الوراثة.

خاصّيّات عقد DOM الرئيسيّة هي:

  • nodeType: يمكننا استعمالها لمعرفة ما إذا كانت العقدة نصيّة أو عنصريّة. لها القيمة العددية: 1 للعناصر و 3 للعقد النصّيّة، وبعض القيم اﻷخرى لباقي أنواع العقد. يمكن قراءتها فقط.
  • nodeName/tagName: للعناصر tagName (تُكتب باﻷحرف الكبيرة ماعدا في وضع XML)، وللعقد غير العنصريّة nodeName. تُبيّن ماهية العقدة. يمكن قراءتها فقط.
  • innerHTML: ما يحتويه العنصر من HTML. يمكن التعديل عليها.
  • outerHTML: كامل HTML العنصر. لا تمسّ عمليّة التعديل على elem.outerHTML العنصر elem نفسه، لكن تستبدله بالـ HTML الجديد في DOM.
  • nodeValue/data: محتوى العقد غير العنصريّة (النصوص والتعليقات). هاتان الخاصّيّتان متماثلتان تقريبا، لكن تُستعمل في العادة data . يمكن التعديل عليها.
  • textContent: النصّ الذي بداخل العنصر: HTML منزوع الوسوم. يضع التعديلُ عليه النصَّ بداخل العنصر، مع معاملة جميع المحرّفات الخاصّة والوسوم كمجرّد نصّ. تُمكّن من إدراج النصوص التي يُدخلها المستخدمون بطريقة آمنة تحمي من إدراج HTML غير مرغوب فيه.
  • hidden: عند إعطائها القيمة true تؤدّي نفس وظيفة display:none في CSS.

تملك عقد DOM المزيد من الخاصّيّات بحسب الصنف الذي تنتمي إليه. على سبيل المثال، تدعم العناصر <input> (ذات الصنف HTMLInputElement) كلاّ من value و type ، بينما تدعم <a> (ذات الصنف HTMLAnchorElement) الخاصيّة href ، إلى غير ذلك. لمُعظم السمات القياسية في HTML خاصيّة تقابلها في DOM.

لكن مع ذلك، لا تتساوى سمات HTML وخاصيّات DOM على الدوام، كما سنرى في المقال التالي.


التمارين

حساب الفروع

اﻷهميّة: 5

هناك شجرة متشكّلة من تفرّع العناصر ul/li.

اكتب الشفرة التي من أجل كل <li> تستعرض التالي:

  1. النصّ الذي بداخله (دون الشجرة الفرعيّة).
  2. عدد الـ <li> المتفرّعة عنه -- جميع العقد السليلة، بما في ذلك العقد المتفرّعة عن فروعه إلى آخر ذلك.

افتح المثال في نافذة مستقلة

افتح البيئة التجريبيّة لإنجاز التمرين

الحل

لنعمل حلقة تكراريّة حول جميع الـ <li>:

for (let li of document.querySelectorAll('li')) {
  ...
}

نحتاج في الحلقة أن نحصل على النصّ بداخل كلّ li.

يمكننا قراءة النصّ الذي بداخل أوّل العقد اﻷبناء لـ li ، والتي هي العقدة النصّيّة:

for (let li of document.querySelectorAll('li')) {
  let title = li.firstChild.data;

  // الذي يأتي قبل باقي العقد <li> هو النصّ داخل title  
}

بعدها يمكننا الحصول على عدد العقد السليلة باستخدام li.getElementsByTagName('li').length.

افتح الحلّ في البيئة التجريبيّة


مالذي بداخل nodeType؟

الأهميّة: 5

ماذا يعرض السكربت التالي:

<html>

<body>
  <script>
    alert(document.body.lastChild.nodeType);
  </script>
</body>

</html>

الحل

توجد خدعة هنا.

في الوقت الذي يُنفّذ فيه <script> ، تكون آخر العقد في DOM هي <script> نفسه، ﻷنّ المتصفّح لم يكن قد عالج بقيّة الصفحة بعد.

إذا الجواب هو 1 (عقدة عنصريّة).

<html>

<body>
  <script>
    alert(document.body.lastChild.nodeType);
  </script>
</body>

</html>

وسم في التعليق

اﻷهميّة: 3

ماذا تعرض هذه الشفرة:

<script>
  let body = document.body;

  body.innerHTML = "<!--" + body.tagName + "-->";

  alert( body.firstChild.data ); // ماذا هنا؟
</script>

الحل

الجواب هو، BODY

<script>
  let body = document.body;

  body.innerHTML = "<!--" + body.tagName + "-->";

  alert( body.firstChild.data ); // BODY
</script>

الذي يحصل خطوة بخطوة:

  1. يُستبدل محتوى <body> بالتعليق. التعليق هو <--BODY--!> ، لأنّ "body.tagName == "BODY. كما نذكر، يٌكتب tagName دائما بالأحرف الكبيرة في HTML.
  2. التعليق الآن هو الابن الوحيد، فنحصل عليه بواسطة body.firstChild.
  3. تكون خاصّيّة data بالنسبة للتعليق هي محتواه (ما بداخل <--...--!>): "BODY".

أين يقع "المستند" في التسلسل الهرمي:

اﻷهميّة: 4

ما هو الصنف الذي ينتمي إليه document ؟

ما هو مكانه في التسلسل الهرميّ لـ DOM ؟

هل يرث من Node أو Element أو ربّما HTMLElement ؟

الحل

يمكننا معرفة الصنف الذي ينتمي إليه من خلال عرضها هكذا:

alert(document); // [object HTMLDocument]

أو:

alert(document.constructor.name); // HTMLDocument

إذًا document هو نسخة من الصنف HTMLDocument.

ما هو مكانه في التسلسل الهرميّ ؟

نعم، يمكننا تصفّح المواصفة، لكن سيكون أسرع لو اكتشفنا ذلك يدويّا.

لنجتز سلسلة prototype بواسطة __proto__.

كما نعلم، توجد توابع الصنف في prototype الدالة البانية. على سبيل المثال، توجد توابع المستندات في HTMLDocument.prototype.

بالإضافة إلى ذلك، هناك مرجعٌ إلى الدالة البانية بداخل prototype:

alert(HTMLDocument.prototype.constructor === HTMLDocument); // true

للحصول على اسم الصنف على شكل سلسلة نصيّة، يمكننا استخدام constructor.name. لنفعل ذلك مع جميع سلسلة prototype الخاصّة بـ document إلى غاية الصنف Node:

alert(HTMLDocument.prototype.constructor.name); // HTMLDocument
alert(HTMLDocument.prototype.__proto__.constructor.name); // Document
alert(HTMLDocument.prototype.__proto__.__proto__.constructor.name); // Node

هذا هو التسلسل الهرميّ.

يمكننا كذلك تفحّص الكائن باستخدام (console.dir(document ثم معرفة هذه اﻷسماء من خلال فتح __proto__. تأخذ الطرفيّة هذه اﻷسماء من constructor داخليّا.


ترجمة وبتصرف لمقال Node properties: type, tag and contents من كتاب The Modern JavaScript Tutorial.


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...