کار با DOM در جاوا اسکریپت: دستکاری عناصر HTML

فهرست مطالب

مقدمه: درک مدل شیء سند (DOM) در جاوا اسکریپت

در دنیای پویای توسعه وب، جاوا اسکریپت به عنوان ستون فقرات تعامل و پویایی در صفحات وب عمل می‌کند. اما این پویایی چگونه محقق می‌شود؟ هسته اصلی این قابلیت، مفهوم مدل شیء سند (Document Object Model – DOM) است. DOM یک رابط برنامه‌نویسی کاربردی (API) برای اسناد HTML و XML فراهم می‌کند. به زبان ساده، DOM یک نمایش ساختاریافته و درختی از یک صفحه وب است که جاوا اسکریپت می‌تواند از طریق آن به عناصر HTML دسترسی پیدا کرده، آن‌ها را تغییر داده، حذف یا عناصر جدیدی را اضافه کند.

تصور کنید یک صفحه HTML مجموعه‌ای از کدها و تگ‌ها است که مرورگر آن‌ها را تفسیر می‌کند. DOM مرورگر این کدها را به یک ساختار سلسله‌مراتبی از اشیاء تبدیل می‌کند، جایی که هر گره (Node) در این درخت، یک بخش از سند (مانند یک عنصر HTML، یک متن یا یک صفت) را نشان می‌دهد. این ساختار درختی، به جاوا اسکریپت اجازه می‌دهد تا با استفاده از متدها و خصوصیات مختلف، به هر جزء از صفحه وب دسترسی داشته باشد و آن را دستکاری کند.

اهمیت DOM در توانایی آن برای تبدیل یک صفحه وب ایستا به یک تجربه کاربری پویا و تعاملی نهفته است. بدون DOM، جاوا اسکریپت تنها قادر به اجرای عملیات منطقی و محاسباتی می‌بود و امکان تغییر محتوا، استایل‌ها، و پاسخ به رویدادهای کاربر (مانند کلیک یا ورودی صفحه کلید) را نداشت. تسلط بر DOM، سنگ بنای توسعه فرانت‌اند مدرن است و به شما این امکان را می‌دهد که رابط‌های کاربری پیچیده و واکنش‌گرا ایجاد کنید.

در این مقاله جامع، به بررسی عمیق کار با DOM در جاوا اسکریپت خواهیم پرداخت. از انتخاب عناصر گرفته تا دستکاری محتوا و ویژگی‌ها، از ایجاد و حذف عناصر تا مدیریت رویدادها و بهینه‌سازی عملکرد. هدف ما این است که شما را با تمام ابزارهای لازم برای تسلط بر DOM و ایجاد تجربیات وب بی‌نظیر مجهز کنیم.

ساختار درختی DOM: آناتومی یک صفحه وب

برای اینکه بتوانیم به طور مؤثر با DOM کار کنیم، درک ساختار آن ضروری است. DOM یک نمایش منطقی و درختی از یک سند HTML است که در آن هر جزء از سند به عنوان یک گره (Node) در نظر گرفته می‌شود. این گره‌ها می‌توانند انواع مختلفی داشته باشند:

  • گره عنصر (Element Node): هر تگ HTML مانند <div>، <p>، <a>، <img> یک گره عنصر را تشکیل می‌دهد. این‌ها رایج‌ترین نوع گره‌هایی هستند که با آن‌ها کار می‌کنیم.
  • گره متن (Text Node): محتوای متنی درون عناصر، مانند “سلام دنیا!” درون <p>سلام دنیا!</p>، به عنوان یک گره متن در نظر گرفته می‌شود.
  • گره صفت (Attribute Node): هر صفت (Attribute) یک عنصر HTML، مانند href در <a href=”url”>، یک گره صفت را تشکیل می‌دهد. توجه داشته باشید که این گره‌ها مستقیماً بخشی از درخت اصلی DOM نیستند، بلکه به گره‌های عنصر مرتبط هستند.
  • گره کامنت (Comment Node): کامنت‌های HTML (<!– … –>) نیز به عنوان گره‌های کامنت در درخت DOM نمایش داده می‌شوند.
  • گره سند (Document Node): بالاترین گره در درخت DOM، شیء document است که نمایانگر کل صفحه وب است. تمامی عناصر HTML زیرمجموعه این گره قرار می‌گیرند.

این گره‌ها روابط سلسله‌مراتبی با یکدیگر دارند: یک گره می‌تواند والد (Parent) گره‌های دیگر باشد، فرزند (Child) گره‌ای دیگر باشد، یا خواهر و برادر (Sibling) گره‌های هم‌سطح خود باشد. به عنوان مثال، در کد زیر:


<div id="container">
    <h1>عنوان اصلی</h1>
    <p>این یک پاراگراف است.</p>
</div>
  • <div id="container"> والد <h1> و <p> است.
  • <h1> و <p> فرزندان <div id="container"> هستند.
  • <h1> و <p> خواهر و برادر یکدیگر هستند.
  • متن “عنوان اصلی” درون <h1> یک گره متن است.

مرورگر هر بار که یک سند HTML را بارگذاری می‌کند، یک نمایش DOM از آن سند ایجاد می‌کند. جاوا اسکریپت سپس از این ساختار درختی برای دسترسی و تغییر پویا در محتوا، استایل و ساختار صفحه استفاده می‌کند.

انتخاب عناصر DOM: یافتن هدف

اولین گام برای دستکاری DOM، یافتن عناصر مورد نظر است. جاوا اسکریپت متد‌های قدرتمندی را برای انتخاب عناصر از درخت DOM فراهم می‌کند. انتخاب دقیق و کارآمد عناصر، اساس عملیات بعدی شما خواهد بود.

document.getElementById()

این متد یکی از رایج‌ترین و سریع‌ترین راه‌ها برای انتخاب یک عنصر است، به شرطی که عنصر دارای صفت id منحصربه‌فرد باشد. id باید در کل سند HTML یکتا باشد.


<div id="myElement">سلام DOM!</div>

<script>
    const myDiv = document.getElementById('myElement');
    console.log(myDiv.textContent); // خروجی: سلام DOM!
</script>

اگر عنصری با id مشخص شده پیدا نشود، null بازگردانده می‌شود.

document.getElementsByClassName()

این متد یک HTMLCollection از تمام عناصری که دارای یک یا چند کلاس CSS مشخص هستند، بازمی‌گرداند. HTMLCollection یک آرایه زنده (Live) است، به این معنی که اگر عناصر جدیدی به DOM اضافه یا حذف شوند که با این کلاس مطابقت دارند، این مجموعه به طور خودکار به‌روز می‌شود.


<div class="item">مورد 1</div>
<p class="item">مورد 2</p>
<span class="item">مورد 3</span>

<script>
    const items = document.getElementsByClassName('item');
    console.log(items.length); // خروجی: 3

    // می‌توان بر روی HTMLCollection حلقه زد
    for (let i = 0; i < items.length; i++) {
        console.log(items[i].textContent);
    }
    // خروجی: مورد 1، مورد 2، مورد 3
</script>

برای کار با این مجموعه مانند یک آرایه معمولی، می‌توانید آن را به یک آرایه تبدیل کنید: Array.from(items).

document.getElementsByTagName()

این متد نیز یک HTMLCollection بازمی‌گرداند که شامل تمام عناصر با یک نام تگ مشخص است. مانند getElementsByClassName، این مجموعه نیز زنده است.


<ul>
    <li>قلم 1</li>
    <li>قلم 2</li>
</ul>
<p>این یک پاراگراف است.</p>

<script>
    const listItems = document.getElementsByTagName('li');
    console.log(listItems.length); // خروجی: 2

    const paragraphs = document.getElementsByTagName('p');
    console.log(paragraphs[0].textContent); // خروجی: این یک پاراگراف است.
</script>

document.querySelector()

این متد مدرن‌ترین و قدرتمندترین روش برای انتخاب عناصر است. querySelector اولین عنصری را بازمی‌گرداند که با یک انتخابگر CSS مشخص مطابقت دارد. شما می‌توانید از هر انتخابگر CSS معتبری (ID، کلاس، تگ، صفت، ترکیبی از آن‌ها و …) استفاده کنید.


<div class="container">
    <p id="firstParagraph">پاراگراف اول.</p>
    <p class="highlight">پاراگراف دوم.</p>
</div>

<script>
    const myDiv = document.querySelector('.container'); // انتخاب با کلاس
    const firstP = document.querySelector('#firstParagraph'); // انتخاب با ID
    const highlightedP = document.querySelector('p.highlight'); // ترکیب تگ و کلاس
    const anyP = document.querySelector('p'); // اولین پاراگراف

    console.log(myDiv.tagName); // خروجی: DIV
    console.log(firstP.textContent); // خروجی: پاراگراف اول.
    console.log(highlightedP.textContent); // خروجی: پاراگراف دوم.
    console.log(anyP.textContent); // خروجی: پاراگراف اول. (فقط اولین مطابقت را برمی‌گرداند)
</script>

اگر عنصری یافت نشود، null بازگردانده می‌شود.

document.querySelectorAll()

برخلاف querySelector که فقط اولین مطابقت را برمی‌گرداند، querySelectorAll یک NodeList از تمام عناصری که با انتخابگر CSS مشخص مطابقت دارند، بازمی‌گرداند. NodeList شبیه به آرایه است و می‌توانید با forEach بر روی آن حلقه بزنید، اما به طور پیش‌فرض، یک NodeList غیرزنده (Static) است، به این معنی که تغییرات بعدی در DOM بر روی آن تأثیری نمی‌گذارد.


<ul>
    <li class="item">مورد A</li>
    <li>مورد B</li>
    <li class="item">مورد C</li>
</ul>

<script>
    const allItems = document.querySelectorAll('li.item');
    console.log(allItems.length); // خروجی: 2

    allItems.forEach(item => {
        console.log(item.textContent);
    });
    // خروجی: مورد A، مورد C
</script>

querySelectorAll به دلیل انعطاف‌پذیری بالا و امکان استفاده از انتخابگرهای پیچیده CSS، در پروژه‌های مدرن بسیار محبوب است.

متدهای پیشرفته‌تر انتخاب و بررسی عناصر

علاوه بر متدهای اصلی انتخاب، جاوا اسکریپت متدهای دیگری را برای بررسی و انتخاب عناصر بر اساس روابط یا محتوا فراهم می‌کند:

  • element.matches(selector): بررسی می‌کند که آیا یک عنصر با انتخابگر CSS مشخص مطابقت دارد یا خیر. true یا false برمی‌گرداند.
    
            const myDiv = document.getElementById('myElement');
            if (myDiv.matches('.container')) {
                console.log('این دیو دارای کلاس container است.');
            }
            
  • element.closest(selector): از عنصر فعلی شروع کرده و به سمت بالا در درخت DOM حرکت می‌کند تا اولین والد (یا خود عنصر) را که با انتخابگر مشخص مطابقت دارد، پیدا کند. اگر چیزی یافت نشود، null برمی‌گرداند. این متد برای دلیگیت کردن رویدادها بسیار مفید است.
    
            <div class="card">
                <button>کلیک کنید</button>
            </div>
    
            <script>
                document.querySelector('button').addEventListener('click', function(event) {
                    const card = event.target.closest('.card');
                    if (card) {
                        console.log('دکمه درون کارد با کلاس:', card.className);
                    }
                });
            </script>
            
  • parentNode.contains(childNode): بررسی می‌کند که آیا یک گره (childNode) فرزند (مستقیم یا غیرمستقیم) یک گره دیگر (parentNode) است یا خیر.
    
            const parentDiv = document.getElementById('parent');
            const childSpan = document.getElementById('child');
            console.log(parentDiv.contains(childSpan)); // خروجی: true
            

دستکاری محتوا و ویژگی‌های عناصر: قلب DOM

پس از انتخاب عناصر، گام بعدی دستکاری محتوا و ویژگی‌های آن‌هاست. این بخش جایی است که پویایی واقعی به صفحه وب شما اضافه می‌شود.

تغییر محتوای متنی و HTML

  • element.textContent: این خصوصیت متن گره را برمی‌گرداند یا تنظیم می‌کند، بدون در نظر گرفتن تگ‌های HTML. این روش از لحاظ امنیتی ایمن‌تر است زیرا هرگونه HTML را به عنوان متن ساده تفسیر می‌کند.
    
            <p id="myParagraph">سلام <strong>دنیا</strong>!</p>
    
            <script>
                const paragraph = document.getElementById('myParagraph');
                console.log(paragraph.textContent); // خروجی: سلام دنیا!
                paragraph.textContent = 'متن جدید.'; // محتوای HTML حذف می‌شود
                console.log(paragraph.innerHTML); // خروجی: متن جدید.
            </script>
            
  • element.innerHTML: این خصوصیت محتوای HTML داخل یک عنصر را برمی‌گرداند یا تنظیم می‌کند. با استفاده از این روش می‌توانید تگ‌های HTML را اضافه یا تغییر دهید. احتیاط: استفاده از innerHTML با ورودی‌های کاربر می‌تواند منجر به آسیب‌پذیری‌های امنیتی Cross-Site Scripting (XSS) شود. همیشه ورودی‌های کاربر را قبل از قرار دادن در innerHTML اعتبار سنجی (sanitize) کنید.
    
            <div id="myDiv"></div>
    
            <script>
                const myDiv = document.getElementById('myDiv');
                myDiv.innerHTML = '<h2>عنوان اضافه شده</h2><p>این یک پاراگراف جدید است.</p>';
            </script>
            
  • element.innerText: شبیه به textContent است اما تفاوت‌های ظریفی دارد. innerText به استایل CSS احترام می‌گذارد و فقط متن قابل مشاهده را برمی‌گرداند. برای مثال، اگر متن با display: none; مخفی شده باشد، innerText آن را برنمی‌گرداند، در حالی که textContent برمی‌گرداند. innerText نیز ممکن است از textContent کندتر باشد زیرا مرورگر باید CSS را اعمال کند.

مدیریت ویژگی‌ها (Attributes)

ویژگی‌های HTML (مانند src، href، class، id) اطلاعات اضافی در مورد عناصر ارائه می‌دهند. DOM متدهایی را برای دستکاری این ویژگی‌ها فراهم می‌کند.

  • element.getAttribute(name): مقدار یک ویژگی را برمی‌گرداند.
    
            <a id="myLink" href="https://example.com">لینک</a>
    
            <script>
                const link = document.getElementById('myLink');
                console.log(link.getAttribute('href')); // خروجی: https://example.com
            </script>
            
  • element.setAttribute(name, value): مقدار یک ویژگی را تنظیم می‌کند. اگر ویژگی وجود نداشته باشد، آن را اضافه می‌کند.
    
            const link = document.getElementById('myLink');
            link.setAttribute('target', '_blank'); // اضافه کردن target="_blank"
            link.setAttribute('href', 'https://new-example.com'); // تغییر href
            
  • element.removeAttribute(name): یک ویژگی را از عنصر حذف می‌کند.
    
            const link = document.getElementById('myLink');
            link.removeAttribute('target'); // حذف target
            
  • element.hasAttribute(name): بررسی می‌کند که آیا یک عنصر دارای ویژگی مشخص است یا خیر. true یا false برمی‌گرداند.
    
            console.log(link.hasAttribute('href')); // خروجی: true
            console.log(link.hasAttribute('data-custom')); // خروجی: false
            

مدیریت استایل‌ها و کلاس‌ها

تغییر ظاهر عناصر از طریق CSS در جاوا اسکریپت نیز بسیار رایج است.

  • element.style.propertyName: می‌توانید خصوصیات استایل CSS را به صورت مستقیم تغییر دهید. نام خصوصیات CSS که شامل خط تیره (-) هستند (مانند background-color)، در جاوا اسکریپت به camelCase تبدیل می‌شوند (مانند backgroundColor). این تغییرات به عنوان استایل‌های “inline” اعمال می‌شوند.
    
            <div id="styledDiv">تغییر استایل</div>
    
            <script>
                const styledDiv = document.getElementById('styledDiv');
                styledDiv.style.backgroundColor = 'blue';
                styledDiv.style.color = 'white';
                styledDiv.style.padding = '10px';
            </script>
            

    توجه: استفاده بیش از حد از element.style معمولاً توصیه نمی‌شود، زیرا می‌تواند مدیریت CSS را دشوار کند. بهتر است از کلاس‌های CSS استفاده کنید.

  • element.classList: این یک API قدرتمند برای مدیریت کلاس‌های CSS یک عنصر است.
    • add(className): یک یا چند کلاس را اضافه می‌کند.
      styledDiv.classList.add('active', 'highlight');
    • remove(className): یک یا چند کلاس را حذف می‌کند.
      styledDiv.classList.remove('active');
    • toggle(className, force): اگر کلاس وجود داشته باشد آن را حذف می‌کند و اگر وجود نداشته باشد آن را اضافه می‌کند. آرگومان دوم اختیاری force، می‌تواند مشخص کند که کلاس باید اضافه (true) یا حذف (false) شود، بدون توجه به وجود آن.
      styledDiv.classList.toggle('visible'); // اگر هست حذف، اگر نیست اضافه
                      styledDiv.classList.toggle('hidden', true); // همیشه اضافه می‌کند
                      
    • contains(className): بررسی می‌کند که آیا عنصر دارای کلاس مشخص است یا خیر.
      console.log(styledDiv.classList.contains('highlight')); // خروجی: true

    استفاده از classList بهترین روش برای مدیریت استایل‌ها است، زیرا منطق ظاهر (CSS) را از منطق تعاملی (JavaScript) جدا نگه می‌دارد.

ایجاد و مدیریت عناصر جدید: ساختاردهی پویا

یکی از قوی‌ترین قابلیت‌های DOM، توانایی ایجاد عناصر جدید HTML و اضافه کردن آن‌ها به صفحه است. این امکان، ساخت رابط‌های کاربری پیچیده و پویا را فراهم می‌کند.

document.createElement(tagName)

این متد یک عنصر HTML جدید با نام تگ مشخص شده ایجاد می‌کند. عنصر ایجاد شده هنوز بخشی از DOM نیست و باید به صورت دستی به درخت اضافه شود.


const newDiv = document.createElement('div');
newDiv.textContent = 'این یک دیو جدید است!';
newDiv.id = 'new-element';
newDiv.classList.add('dynamic-content');

document.createTextNode(text)

یک گره متنی جدید ایجاد می‌کند. این متد برای افزودن متن خام به عناصر بسیار مفید است، به ویژه زمانی که نمی‌خواهید از innerHTML به دلیل مسائل امنیتی استفاده کنید.


const newTextNode = document.createTextNode('این متن از یک گره متنی است.');

اضافه کردن عناصر به DOM

  • parentNode.appendChild(child): یک گره را به عنوان آخرین فرزند به والد مشخص شده اضافه می‌کند. این رایج‌ترین روش برای اضافه کردن عناصر است.
    
            const container = document.getElementById('container');
            const newParagraph = document.createElement('p');
            newParagraph.textContent = 'این یک پاراگراف اضافه شده پویا است.';
            container.appendChild(newParagraph);
            
  • parentNode.insertBefore(newNode, referenceNode): یک گره جدید (newNode) را قبل از یک گره مرجع (referenceNode) به عنوان فرزند parentNode اضافه می‌کند. اگر referenceNode برابر با null باشد، newNode به عنوان آخرین فرزند اضافه می‌شود (که معادل appendChild است).
    
            const list = document.getElementById('myList');
            const firstItem = list.firstElementChild; // اولین آیتم لیست
            const newItem = document.createElement('li');
            newItem.textContent = 'مورد جدید قبل از اولین آیتم';
            list.insertBefore(newItem, firstItem);
            

حذف و جایگزینی عناصر

  • parentNode.removeChild(child): یک گره فرزند مشخص شده را از والد خود حذف می‌کند.
    
            const container = document.getElementById('container');
            const paragraphToRemove = document.getElementById('paragraph-to-remove');
            if (paragraphToRemove) {
                container.removeChild(paragraphToRemove);
            }
            
  • parentNode.replaceChild(newChild, oldChild): یک گره فرزند قدیمی را با یک گره جدید جایگزین می‌کند.
    
            const container = document.getElementById('container');
            const oldParagraph = document.getElementById('old-paragraph');
            const newParagraph = document.createElement('p');
            newParagraph.textContent = 'این پاراگراف جایگزین شد.';
            container.replaceChild(newParagraph, oldParagraph);
            

کپی کردن عناصر: element.cloneNode()

این متد یک کپی از یک عنصر ایجاد می‌کند. آرگومان اختیاری deep (بولین) مشخص می‌کند که آیا فرزندان عنصر نیز باید کپی شوند یا خیر (true برای کپی عمیق، false برای کپی سطحی).


const originalDiv = document.getElementById('original');
const clonedDiv = originalDiv.cloneNode(true); // کپی عمیق شامل فرزندان
clonedDiv.id = 'cloned'; // ID باید منحصر به فرد باشد!
document.body.appendChild(clonedDiv);

insertAdjacentHTML و متدهای مشابه

این متدها راهی قدرتمند و کارآمد برای درج HTML، عنصر یا متن در مکان‌های مشخص شده نسبت به یک عنصر موجود فراهم می‌کنند، بدون اینکه محتوای موجود عنصر را بازنویسی کنند (برخلاف innerHTML).

متد element.insertAdjacentHTML(position, text) دو آرگومان می‌گیرد:

  • position: یک رشته که نشان‌دهنده مکان درج است:
    • 'beforebegin': قبل از خود عنصر.
    • 'afterbegin': درست داخل عنصر، قبل از اولین فرزندش.
    • 'beforeend': درست داخل عنصر، بعد از آخرین فرزندش.
    • 'afterend': بعد از خود عنصر.
  • text: رشته HTML که قرار است درج شود.

<div id="myDiv">
    <p>متن اصلی دیو.</p>
</div>

<script>
    const myDiv = document.getElementById('myDiv');

    myDiv.insertAdjacentHTML('beforebegin', '<p>قبل از دیو.</p>');
    myDiv.insertAdjacentHTML('afterbegin', '<h3>اولین فرزند دیو.</h3>');
    myDiv.insertAdjacentHTML('beforeend', '<span>آخرین فرزند دیو.</span>');
    myDiv.insertAdjacentHTML('afterend', '<div>بعد از دیو.</div>');
</script>

متدهای مشابه عبارتند از insertAdjacentElement(position, element) برای درج یک گره عنصر و insertAdjacentText(position, text) برای درج متن ساده.

مدیریت رویدادها: پاسخ به تعاملات کاربر

یکی از مهم‌ترین جنبه‌های DOM، قابلیت پاسخ به رویدادها (Events) است. رویدادها اقداماتی هستند که توسط کاربر (مانند کلیک ماوس، فشردن کلید، ارسال فرم) یا توسط مرورگر (مانند بارگذاری صفحه) اتفاق می‌افتند.

element.addEventListener(event, handler, options)

این متد مدرن‌ترین و توصیه شده‌ترین راه برای ثبت شنونده‌های رویداد است. این متد به شما اجازه می‌دهد تا چندین شنونده را برای یک رویداد بر روی یک عنصر ثبت کنید و انعطاف‌پذیری بیشتری در کنترل رویدادها فراهم می‌کند.

  • event: نام رویداد (مثلاً 'click'، 'mouseover'، 'submit'، 'load').
  • handler: تابعی که هنگام وقوع رویداد اجرا می‌شود.
  • options: یک شیء اختیاری که گزینه‌های پیشرفته‌ای مانند capture، once، passive را ارائه می‌دهد.

<button id="myButton">کلیک کنید</button>

<script>
    const button = document.getElementById('myButton');

    button.addEventListener('click', function() {
        alert('دکمه کلیک شد!');
    });

    // اضافه کردن یک شنونده دیگر برای همان رویداد
    button.addEventListener('click', () => {
        console.log('رویداد کلیک در کنسول ثبت شد.');
    });

    // رویداد 'mouseover'
    button.addEventListener('mouseover', function() {
        this.style.backgroundColor = 'lightblue';
    });

    // رویداد 'mouseout'
    button.addEventListener('mouseout', function() {
        this.style.backgroundColor = ''; // بازگرداندن به حالت اولیه
    });
</script>

شیء رویداد (Event Object)

هنگامی که یک رویداد اتفاق می‌افتد، یک شیء Event به تابع شنونده ارسال می‌شود. این شیء حاوی اطلاعات مفیدی در مورد رویداد است:

  • event.target: عنصری که رویداد در آنجا شروع شده است (عنصری که واقعاً کلیک شده است).
  • event.currentTarget: عنصری که شنونده رویداد به آن متصل است.
  • event.type: نوع رویداد (مثلاً ‘click’).
  • event.preventDefault(): از رفتار پیش‌فرض مرورگر برای آن رویداد جلوگیری می‌کند (مثلاً از ارسال فرم یا پرش لینک جلوگیری می‌کند).
    
            document.getElementById('myLink').addEventListener('click', function(event) {
                event.preventDefault(); // جلوگیری از پرش به لینک
                alert('لینک کلیک شد اما به جای دیگری نرفت!');
            });
            
  • event.stopPropagation(): از گسترش (bubbling) رویداد به عناصر والد جلوگیری می‌کند.
    
            <div id="outer">
                <button id="inner">درونی</button>
            </div>
    
            <script>
                document.getElementById('outer').addEventListener('click', () => {
                    console.log('بیرونی کلیک شد');
                });
    
                document.getElementById('inner').addEventListener('click', (event) => {
                    event.stopPropagation(); // جلوی گسترش به outer را می‌گیرد
                    console.log('درونی کلیک شد');
                });
            </script>
            

element.removeEventListener(event, handler, options)

برای حذف یک شنونده رویداد، باید تابع شنونده دقیقاً همان تابعی باشد که هنگام اضافه کردن استفاده شده است. نمی‌توانید یک تابع بی‌نام (anonymous function) را حذف کنید.


function handleClick() {
    alert('این پیام فقط یک بار ظاهر می‌شود.');
    button.removeEventListener('click', handleClick); // حذف شنونده بعد از یک بار اجرا
}
button.addEventListener('click', handleClick);

دلیگیت کردن رویدادها (Event Delegation)

به جای اضافه کردن شنونده رویداد به هر عنصر فرزند به صورت جداگانه (به ویژه در لیست‌های طولانی یا عناصر ایجاد شده پویا)، می‌توانید شنونده را به یک والد مشترک اضافه کنید. سپس، با استفاده از event.target، می‌توانید تشخیص دهید کدام فرزند رویداد را آغاز کرده است.

مزایای دلیگیت کردن رویدادها:

  • کارایی: تعداد شنونده‌های رویداد در DOM را کاهش می‌دهد، که منجر به بهبود عملکرد می‌شود.
  • پویایی: به طور خودکار رویدادها را برای عناصر جدیدی که به DOM اضافه می‌شوند، مدیریت می‌کند.

<ul id="myList">
    <li>مورد 1</li>
    <li>مورد 2</li>
    <li>مورد 3</li>
</ul>

<script>
    const list = document.getElementById('myList');

    list.addEventListener('click', function(event) {
        if (event.target.tagName === 'LI') { // بررسی کنید که آیا عنصر کلیک شده یک LI است
            alert('شما روی مورد ' + event.target.textContent + ' کلیک کردید.');
        }
    });

    // اگر بعداً یک مورد جدید به لیست اضافه کنیم، این دلیگیت کار خواهد کرد:
    const newItem = document.createElement('li');
    newItem.textContent = 'مورد جدید';
    list.appendChild(newItem);
</script>

پیمایش در DOM: حرکت در درخت

گاهی اوقات نیاز دارید که از یک عنصر به عناصر مرتبط با آن (والد، فرزند، خواهر و برادر) دسترسی پیدا کنید. DOM خصوصیات مختلفی برای پیمایش درخت فراهم می‌کند.

والدین (Parents)

  • element.parentNode: گره والد را برمی‌گرداند (می‌تواند هر نوع گره‌ای باشد، از جمله گره‌های متن).
    
            <div id="container">
                <p id="child">متن.</p>
            </div>
    
            <script>
                const child = document.getElementById('child');
                console.log(child.parentNode.id); // خروجی: container
            </script>
            
  • element.parentElement: عنصر والد را برمی‌گرداند. این خصوصیت فقط عناصر را برمی‌گرداند (در مقایسه با parentNode که ممکن است گره‌های غیرعنصر مانند گره‌های متن را برگرداند). این معمولاً ترجیح داده می‌شود.

فرزندان (Children)

  • element.childNodes: یک NodeList از تمام گره‌های فرزند را برمی‌گرداند، از جمله گره‌های متن و کامنت.
    
            <div id="parent">
                متن 1
                <span>اسپن</span>
                متن 2
            </div>
    
            <script>
                const parent = document.getElementById('parent');
                console.log(parent.childNodes.length); // ممکن است شامل گره‌های متنی فضای خالی نیز باشد
                parent.childNodes.forEach(node => console.log(node.nodeType, node.nodeName));
            </script>
            

    nodeType 1 برای عنصر، 3 برای متن، 8 برای کامنت است. nodeName نام تگ برای عناصر یا #text/#comment برای گره‌های دیگر است.

  • element.children: یک HTMLCollection از فقط گره‌های فرزند عنصر را برمی‌گرداند. این خصوصیت بسیار مفیدتر است زیرا فقط عناصر HTML را شامل می‌شود و گره‌های متن یا کامنت را نادیده می‌گیرد.
    
            const parent = document.getElementById('parent');
            console.log(parent.children.length); // خروجی: 1 (فقط span)
            console.log(parent.children[0].tagName); // خروجی: SPAN
            
  • element.firstElementChild: اولین گره فرزند عنصر را برمی‌گرداند.
    console.log(parent.firstElementChild.tagName); // خروجی: SPAN
  • element.lastElementChild: آخرین گره فرزند عنصر را برمی‌گرداند.

خواهر و برادر (Siblings)

  • element.nextSibling: گره هم‌سطح بعدی (شامل گره‌های متن و کامنت).
  • element.previousSibling: گره هم‌سطح قبلی (شامل گره‌های متن و کامنت).
  • element.nextElementSibling: عنصر هم‌سطح بعدی (فقط عناصر). این روش توصیه می‌شود.
    
            <p>پاراگراف 1</p>
            <div id="myDiv">دیو</div>
            <p>پاراگراف 2</p>
    
            <script>
                const myDiv = document.getElementById('myDiv');
                console.log(myDiv.previousElementSibling.tagName); // خروجی: P
                console.log(myDiv.nextElementSibling.tagName); // خروجی: P
            </script>
            
  • element.previousElementSibling: عنصر هم‌سطح قبلی (فقط عناصر).

ملاحظات عملکرد و بهترین روش‌ها: کارآمدی DOM

دستکاری DOM می‌تواند پرهزینه باشد و اگر به درستی انجام نشود، می‌تواند منجر به کندی عملکرد وب‌سایت شود. مرورگرها برای اعمال تغییرات در DOM، نیاز به انجام عملیات Reflow (محاسبه مجدد موقعیت و اندازه عناصر) و Repaint (بازسازی پیکسل‌های صفحه) دارند که زمان‌بر هستند. در ادامه به برخی بهترین روش‌ها برای بهینه‌سازی عملکرد DOM می‌پردازیم:

کاهش دستکاری‌های DOM

هر بار که شما DOM را دستکاری می‌کنید، مرورگر ممکن است مجبور شود Reflow و Repaint را انجام دهد. سعی کنید تعداد این عملیات را به حداقل برسانید:

  • تغییرات دسته‌ای: به جای انجام چندین تغییر DOM به صورت جداگانه، آن‌ها را در یک دسته انجام دهید.
    
            // بد: چندین تغییر DOM متوالی
            const list = document.getElementById('myList');
            for (let i = 0; i < 100; i++) {
                const li = document.createElement('li');
                li.textContent = `مورد ${i}`;
                list.appendChild(li);
            }
    
            // خوب: ایجاد عناصر در حافظه و اضافه کردن یکباره
            const listEfficient = document.getElementById('myListEfficient');
            const fragment = document.createDocumentFragment();
            for (let i = 0; i < 100; i++) {
                const li = document.createElement('li');
                li.textContent = `مورد ${i}`;
                fragment.appendChild(li);
            }
            listEfficient.appendChild(fragment); // فقط یکبار DOM دستکاری می‌شود
            
  • استفاده از DocumentFragment: DocumentFragment یک شیء DOM سبک وزن است که می‌توانید عناصر را به آن اضافه کنید و سپس یکباره آن را به DOM اصلی اضافه کنید. این کار فقط یک عملیات Reflow/Repaint را الزامی می‌کند، حتی اگر صدها عنصر را اضافه کنید.
  • پنهان کردن عناصر هنگام تغییر: اگر در حال اعمال تغییرات گسترده‌ای بر روی یک عنصر هستید، می‌توانید آن را به طور موقت با display: none; پنهان کنید، تغییرات را اعمال کنید، و سپس آن را دوباره قابل مشاهده کنید. این کار تعداد Reflowها را به یک مورد کاهش می‌دهد.

دلیگیت کردن رویدادها (Event Delegation)

همانطور که قبلاً ذکر شد، دلیگیت کردن رویدادها به جای اضافه کردن شنونده به تک تک عناصر، تعداد شنونده‌های رویداد در حافظه را کاهش می‌دهد و عملکرد را بهبود می‌بخشد، به ویژه برای لیست‌های بلند یا عناصر پویا.

کش کردن مراجع DOM

جستجو در DOM با متدهایی مانند getElementById یا querySelector هزینه‌بر است. اگر قرار است چندین بار به یک عنصر خاص دسترسی داشته باشید، آن را در یک متغیر ذخیره کنید:


// بد: هر بار جستجوی DOM
document.getElementById('myButton').addEventListener('click', function() {
    document.getElementById('myStatus').textContent = 'کلیک شد!';
});

// خوب: کش کردن مراجع
const myButton = document.getElementById('myButton');
const myStatus = document.getElementById('myStatus');
myButton.addEventListener('click', function() {
    myStatus.textContent = 'کلیک شد!';
});

اجتناب از دسترسی مکرر به ویژگی‌های layout-triggering

برخی از ویژگی‌ها و متدهای DOM (مانند offsetWidth، offsetHeight، getComputedStyle) مرورگر را مجبور می‌کنند که Reflow فوری را انجام دهد تا مقادیر صحیح را برگرداند. دسترسی مکرر به این خصوصیات در حلقه‌ها می‌تواند منجر به مشکلات عملکردی شود (شناخته شده به عنوان “layout thrashing”). سعی کنید این مقادیر را کش کنید یا دسترسی به آن‌ها را به حداقل برسانید.

امنیت: محافظت در برابر XSS

همیشه ورودی‌های کاربر را قبل از درج در DOM (به خصوص با innerHTML) اعتبار سنجی و پاک‌سازی کنید. مهاجمان می‌توانند کد مخرب (مانند تگ‌های <script>) را از طریق ورودی‌های کاربر تزریق کنند که منجر به حملات Cross-Site Scripting (XSS) می‌شود. استفاده از textContent به جای innerHTML برای متن ساده ایمن‌تر است.

مفاهیم پیشرفته و API‌های مدرن

جامعه وب دائماً در حال تکامل است و API‌های جدیدی برای تعامل با DOM و بهبود عملکرد معرفی می‌شوند. در حالی که پوشش کامل آن‌ها فراتر از دامنه این مقاله است، آشنایی با برخی از آن‌ها می‌تواند افق دید شما را گسترش دهد.

MutationObserver

این API به شما اجازه می‌دهد تا تغییرات در درخت DOM را مشاهده کنید. شما می‌توانید تغییرات مربوط به اضافه یا حذف شدن گره‌ها، تغییرات در ویژگی‌ها، یا تغییرات در محتوای متنی را پیگیری کنید. این برای ساخت ابزارهای اشکال‌زدایی، افزونه‌های مرورگر، یا واکنش به تغییرات پویا در DOM که توسط سایر اسکریپت‌ها انجام می‌شود، بسیار قدرتمند است.


// یک نمونه MutationObserver جدید ایجاد کنید
const observer = new MutationObserver(mutationsList => {
    for (const mutation of mutationsList) {
        if (mutation.type === 'childList') {
            console.log('گره‌های فرزند اضافه یا حذف شدند.', mutation.addedNodes, mutation.removedNodes);
        } else if (mutation.type === 'attributes') {
            console.log('صفت ' + mutation.attributeName + ' تغییر کرد.');
        }
    }
});

// شروع به مشاهده تغییرات در یک عنصر و فرزندان آن کنید
const targetNode = document.getElementById('container');
const config = { attributes: true, childList: true, subtree: true }; // مشاهده صفات، فرزندان و زیردرخت

observer.observe(targetNode, config);

// برای مثال، یک تغییر ایجاد کنید تا observer فعال شود
// targetNode.appendChild(document.createElement('span'));
// targetNode.setAttribute('data-new', 'value');

requestAnimationFrame

برای انجام انیمیشن‌های مبتنی بر DOM، استفاده از requestAnimationFrame به جای setTimeout یا setInterval بسیار توصیه می‌شود. این متد مرورگر را قادر می‌سازد تا انیمیشن‌ها را بهینه کند و آن‌ها را با نرخ فریم مرورگر همگام‌سازی کند، که منجر به انیمیشن‌های نرم‌تر و کارآمدتر می‌شود.

Shadow DOM (DOM پنهان)

Shadow DOM یک تکنولوژی وب است که به توسعه‌دهندگان اجازه می‌دهد تا یک درخت DOM جداگانه و محصور شده را در داخل یک عنصر خاص ایجاد کنند. این کار به کپسوله‌سازی استایل‌ها و رفتارها کمک می‌کند و از تداخل آن‌ها با بقیه صفحه جلوگیری می‌کند. این مفهوم اساس Web Components است و برای ساخت کامپوننت‌های UI قابل استفاده مجدد و مستقل بسیار مهم است.

Virtual DOM (DOM مجازی)

این یک مفهوم است که توسط فریم‌ورک‌های جاوا اسکریپت مانند React، Vue و Svelte به کار گرفته می‌شود. به جای دستکاری مستقیم DOM، این فریم‌ورک‌ها یک کپی سبک وزن (Virtual DOM) از DOM واقعی را در حافظه نگه می‌دارند. هنگامی که تغییری در حالت برنامه ایجاد می‌شود، آن‌ها Virtual DOM را به‌روزرسانی کرده، تفاوت‌ها را با Virtual DOM قبلی محاسبه می‌کنند و سپس فقط حداقل تغییرات لازم را در DOM واقعی اعمال می‌کنند. این رویکرد به طور قابل توجهی عملکرد را در برنامه‌های وب پیچیده بهبود می‌بخشد، زیرا تعداد عملیات DOM واقعی را به حداقل می‌رساند.

نتیجه‌گیری

مدل شیء سند (DOM) و توانایی جاوا اسکریپت برای دستکاری آن، نیروی محرکه پشت تعامل و پویایی در وب مدرن است. از انتخاب عناصر گرفته تا تغییر محتوا، مدیریت استایل‌ها، و پاسخ به رویدادهای کاربر، درک عمیق از DOM یک مهارت اساسی برای هر توسعه‌دهنده فرانت‌اند است.

در این مقاله، ما به بررسی جامع اصول انتخاب، دستکاری، ایجاد و حذف عناصر، و همچنین مدیریت رویدادها پرداختیم. ما همچنین به اهمیت ملاحظات عملکردی و بهترین روش‌ها برای جلوگیری از مشکلات رایج اشاره کردیم. با به کارگیری تکنیک‌هایی مانند استفاده از DocumentFragment، دلیگیت کردن رویدادها، و کش کردن مراجع DOM، می‌توانید اطمینان حاصل کنید که برنامه‌های وب شما سریع و واکنش‌گرا باقی می‌مانند.

همانطور که به پیش می‌روید، به خاطر داشته باشید که وب یک اکوسیستم در حال تکامل است. کاوش در APIهای پیشرفته‌تر مانند MutationObserver و مفاهیمی مانند Shadow DOM و Virtual DOM، به شما کمک می‌کند تا با جدیدترین روندهای توسعه وب همگام باشید و برنامه‌های وب پیچیده‌تر و کارآمدتری بسازید. تسلط بر DOM یک سفر مداوم است، اما پایه‌هایی که امروز آموختید، شما را در این مسیر به خوبی هدایت خواهند کرد.

“تسلط به برنامه‌نویسی پایتون با هوش مصنوعی: آموزش کدنویسی هوشمند با ChatGPT”

قیمت اصلی 2.290.000 ریال بود.قیمت فعلی 1.590.000 ریال است.

"تسلط به برنامه‌نویسی پایتون با هوش مصنوعی: آموزش کدنویسی هوشمند با ChatGPT"

"با شرکت در این دوره جامع و کاربردی، به راحتی مهارت‌های برنامه‌نویسی پایتون را از سطح مبتدی تا پیشرفته با کمک هوش مصنوعی ChatGPT بیاموزید. این دوره، با بیش از 6 ساعت محتوای آموزشی، شما را قادر می‌سازد تا به سرعت الگوریتم‌های پیچیده را درک کرده و اپلیکیشن‌های هوشمند ایجاد کنید. مناسب برای تمامی سطوح با زیرنویس فارسی حرفه‌ای و امکان دانلود و تماشای آنلاین."

ویژگی‌های کلیدی:

بدون نیاز به تجربه قبلی برنامه‌نویسی

زیرنویس فارسی با ترجمه حرفه‌ای

۳۰ ٪ تخفیف ویژه برای دانشجویان و دانش آموزان