لنتناول عُقدَ DOM بمزيدٍ من التعمّق. في هذا المقال، سننظر أكثر في ماهيّتها وسنتعرّف على أكثر خاصّيّاتها استخداما.
أصناف العقد في DOM
قد تختلف خاصّيات العقد باختلاف العقد نفسها. فعلى سبيل المثال، تملك العقد التي تمثّل الوسم <a>
خاصّيّات تتعلّق بالروابط، وتملك العقد التي تمثّل الوسم <input>
خاصّيّات تتعلّق بالمُدخلات، وهكذا. لكن بالرغم من اختلاف العقد عن بعضها، فإنّ لها أيضا خاصّيّات وتوابع مشتركة بينها، ﻷنّ جميع أصناف العقد في DOM تُشكّل تسلسلا هرميّا واحدا.
تنتمي كلّ عقدة من DOM إلى صنف معيّن من اﻷصناف المسبقة البناء (built-in class).
يكون جذر التسلسل الهرميّ هو الصنف EventTarget، الذي يرث عنه الصنفُ Node، الذي يرث عنه بدوره باقي العقد في DOM.
هذه صورة للتسلسل الهرمي، وسيتبعها المزيد من الشرح:
تتمثّل اﻷصناف في:
- 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>
-
يحصُل
<div>
اﻷوّل على الاسم (name) "على شكل HTML": جميع الوسوم تُعامل كوسوم، ونرى بذلك الاسم بخطّ عريض (bold). -
يحصُل
<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>
تستعرض التالي:
- النصّ الذي بداخله (دون الشجرة الفرعيّة).
-
عدد الـ
<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>
الذي يحصل خطوة بخطوة:
-
يُستبدل محتوى
<body>
بالتعليق. التعليق هو<--BODY--!>
، لأنّ"body.tagName == "BODY
. كما نذكر، يٌكتبtagName
دائما بالأحرف الكبيرة في HTML. -
التعليق الآن هو الابن الوحيد، فنحصل عليه بواسطة
body.firstChild
. -
تكون خاصّيّة
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.
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.