یکی از مفاهیم مهم در رابطه با رویدادهای DOM، مفهوم Event Flow یا Event Propagation است.که توی این پست سعی کردم با زبانی ساده توضیحش بدم. برای توضیح این مفهوم ابتدا یک سند HTML را به صورت زیر تعریف میکنیم.
<html> <head> <title>Event Flow</title> </head> <body> <div id="div1">Click Me</div> </body> </html>
اگر کاربر بر روی عنصر <div> کلیک کند در این صورت رویداد click برای این عنصر رخ میدهد. و اگر یک Event Handler برای این رویداد تعریف شده باشد، فراخوانی خواهد شد.
کلیک کردن بر روی عنصر <div>، به معنی کلیک کردن بر روی عنصر <body> میباشد,زیرا عنصر <div> بخشی از عنصر <body> است.حال فرض کنید یک Event Handler نیز برای رویداد click از عنصر <body> تعریف شده است با کلیک کردن بر روی عنصر <div>، این Event Handler نیز اجرا میشود.به زبانی دیگر هر رویدادی که برای یک عنصر خاص رخ میدهد، برای عنصر والد آن نیز رخ میدهد. همچنین اگر عنصر والد، فرزند عنصری دیگر باشد، این رویداد برای والد آن نیز رخ میدهد. و این روند تا ریشهی درخت DOM، یعنی شئ document ادامه پیدا میکند.
دوحالت برای اجرای ها Event Handler وجود دارد.
حالت اول : ابتدا Event Handler عنصر والد اجرا شود و Event Handler عنصر فرزند پس از آن اجرا شود که به آن Capturing می گویند.
حالت دوم: معکوس حالت اول است.یعنی ابتدا Event Handler عنصر فرزند اجرا شود و Event Handler عنصر والد پس از آن اجرا شود که به آن Bubbling می گویند. شکل زیر این دو حالت را نمایش میدهد.
روشی که امروزه در تمام مرورگرها به کار میرود، ترکیبی از این دو حالت است. به این صورت که ابتدا رویداد برای شئ document رخ میدهد. سپس همین رویداد برای فرزندان شئ document تا رسیدن به عنصر نهایی رخ میدهد. در مرحلهی بعدی همین روند به صورت معکوس تا رسیدن به شئ document ادامه مییابد. شکل زیر ساختار استاندارد انتشار رویداد در DOM را نشان میدهد.
برای نشان دادن مفهوم Event Flow به صورت عملی، به مثال زیر توجه کنید.
const div = document.querySelector('#div1'); const body = document.body; div.addEventListener('click' , divHandler); body.addEventListener('click' , bodyHandler); function divHandler(){ console.log('divHandler'); } function bodyHandler(){ console.log('bodyHandler'); }
حال در صورتی که روی عنصر <div> کلیک کنید، رویداد click ابتدا برای عنصر <div> و پس از آن برای عنصر <body> رخ میدهد. در نتیجه ابتدا تابع divHandler و سپس تابع bodyHandler اجرا میشوند.
به صورت پیشفرض، Event Handler ها فقط در فاز Bubbling اجرا میشوند. اما میتوان حالت پیشفرض را به راحتی تغییر داد و توابعی را به عنوان Event Handler تعریف کرد که در فاز Capturing اجرا شوند. برای انجام این کار میتوان از آرگومان سوم در متد addEventListener استفاده کرد که باید آن را برابر با true قرار دهیم.
body.addEventListener('click' , bodyHandler , true);
حال با کلیک کردن بر روی عنصر <div>، ابتدا تابع bodyHandler و سپس تابع divHandler اجرا میشوند.
همچنین ما می توانیم از اجرای یک رویداد با استفاده از متد stopPropagation که در شئ event وجد دارد را بگیریم.اما استفاده از متد stopPropagation در فازهای Capturing و Bubbling به ندرت اتفاق میافتد. و معمولاً از این متد در فاز Target استفاده میشود.
const div = document.querySelector('#div1'); const body = document.body; div.addEventListener('click' , divHandler); body.addEventListener('click' , bodyHandler); function divHandler(event){ console.log('I`m divHandler'); event.stopPropagation(); } function bodyHandler(){ console.log('I`m bodyHandler'); }
در این مثال در تابع divHandler از متد stopPropagation استفاده شده است. بنابراین اگر بر روی عنصر <div> کلیک کنید، فقط تابع divHandler اجرا میشود. زیرا جریان انتشار رویداد در این تابع قطع شده و فاز Bubbling اجرا نمیشود. البته هنوز هم با کلیک کردن روی عنصر <body> تابع bodyHandler اجرا میشود. زیرا در این حالت عنصر <div> جزئی از ساختار Event Flow نیست.