سفر یک رویداد در DOM: از Capturing تا Bubbling

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

سه مرحله‌ی انتشار رویداد

هر رویداد در DOM از سه مرحله عبور می‌کند:

  • مرحله‌ی Capturing (فرورفتن): رویداد از سند (document) شروع شده و لایه‌به‌لایه به سمت عنصر هدف پایین می‌آید.
  • مرحله‌ی Target: رویداد دقیقاً به عنصری که رویداد روی آن رخ داده می‌رسد.
  • مرحله‌ی Bubbling (بالا‌آمدن): رویداد از عنصر هدف شروع کرده و به سمت بالا، لایه‌به‌لایه تا document بازمی‌گردد.

به‌طور پیش‌فرض، شنونده‌های رویداد (event listeners) در مرحله‌ی Bubbling اجرا می‌شوند؛ یعنی وقتی روی یک دکمه‌ی داخل چند لایه‌ی تودرتو کلیک می‌کنید، ابتدا شنونده‌ی خود دکمه اجرا می‌شود و سپس این رویداد به شنونده‌های عناصر والد می‌رسد، مگر آنکه صریحاً جلوی آن گرفته شود.

کنترل مرحله‌ی اجرا با آرگومان سوم addEventListener

متد addEventListener یک آرگومان سوم اختیاری می‌پذیرد که تعیین می‌کند شنونده در کدام مرحله فعال شود:

element.addEventListener("click", handler, useCapture);

اگر useCapture برابر true باشد، شنونده در مرحله‌ی Capturing اجرا می‌شود؛ مقدار پیش‌فرض آن false است که همان اجرا در مرحله‌ی Bubbling را نتیجه می‌دهد. در پروژه‌های واقعی، بیش از ۹۹ درصد شنونده‌ها با همین مقدار پیش‌فرض نوشته می‌شوند و استفاده از Capturing معمولاً به سناریوهای خاصی مثل رهگیری رویداد پیش از رسیدن به عنصر هدف محدود است.

مشاهده‌ی عملی ترتیب اجرا

برای درک بهتر، سه لایه‌ی تودرتو را در نظر بگیرید که هر کدام هم یک شنونده‌ی Bubbling و هم یک شنونده‌ی Capturing دارند. با کلیک روی داخلی‌ترین عنصر می‌توانید ترتیب دقیق اجرا را مشاهده کنید:

ترتیب اجرای Capturing و Bubbling
خروجی کنسول اینجا نمایش داده می‌شود...

همان‌طور که در خروجی مشخص است، مرحله‌ی Capturing همیشه از بیرونی‌ترین عنصر به سمت هدف حرکت می‌کند و مرحله‌ی Bubbling دقیقاً برعکسِ آن، از هدف به سمت بیرون است.

متوقف کردن انتشار رویداد

گاهی لازم است رویداد به لایه‌های بالاتر یا پایین‌تر نرسد. متد stopPropagation() که روی شیء رویداد فراخوانی می‌شود، دقیقاً همین کار را انجام می‌دهد:

inner.addEventListener("click", (event) => {
  event.stopPropagation();
  console.log("این رویداد دیگر به لایه‌های بالاتر نمی‌رسد");
});

نکته‌ی مهم این است که stopPropagation فقط جلوی رسیدن رویداد به لایه‌های دیگر را می‌گیرد و هیچ ارتباطی با رفتار پیش‌فرض عنصر (مثل ارسال فرم یا دنبال‌کردن لینک) ندارد؛ برای آن، متد جداگانه‌ی preventDefault() استفاده می‌شود که کاربردی کاملاً متفاوت دارد.

جدول مقایسه‌ی Capturing و Bubbling

ویژگی Capturing Bubbling
جهت حرکت رویداد از بیرون به داخل (document به سمت هدف) از داخل به بیرون (هدف به سمت document)
مقدار آرگومان سوم addEventListener true false (پیش‌فرض)
میزان استفاده در عمل محدود و تخصصی رایج‌ترین حالت در پروژه‌های واقعی
کاربرد اصلی رهگیری زودهنگام رویداد پیش از رسیدن به هدف Event Delegation و مدیریت عمومی رویدادها

نکات کلیدی

  • هر رویداد از سه مرحله عبور می‌کند: Capturing، Target، و Bubbling.
  • پیش‌فرض addEventListener اجرای شنونده در مرحله‌ی Bubbling است.
  • stopPropagation() انتشار رویداد را متوقف می‌کند، اما رفتار پیش‌فرض مرورگر را لغو نمی‌کند.
  • درک Bubbling پیش‌نیاز مستقیم مبحث Event Delegation در درس‌های بعدی است.

جمع‌بندی

انتشار رویداد در DOM طی دو مسیر رفت (Capturing) و برگشت (Bubbling) اتفاق می‌افتد و عنصر هدف بین این دو مسیر قرار دارد. در عمل، بیشترین استفاده از مرحله‌ی Bubbling است که پایه‌ی الگوی بسیار مهم Event Delegation نیز محسوب می‌شود؛ الگویی که در درس‌های آینده به‌تفصیل بررسی خواهد شد. کنترل دقیق این رفتار با متدهایی مثل stopPropagation() امکان‌پذیر است، اما باید با احتیاط و شناخت کافی از پیامدهای آن به کار گرفته شود.