preventDefault
و stopPropagation
: زمان استفاده از کدام روش و دقیقاً چه کاری انجام می شود.
Event.stopPropagation() و Event.preventDefault()
مدیریت رویداد جاوا اسکریپت اغلب ساده است. این امر به ویژه هنگامی که با ساختار HTML ساده (نسبتاً مسطح) سروکار داریم، صادق است. وقتی رویدادها از طریق سلسله مراتبی از عناصر در حال حرکت (یا انتشار) هستند، همه چیز کمی بیشتر درگیر می شود. این معمولاً زمانی است که توسعهدهندگان برای حل مشکلاتی که با آن مواجه هستند به سراغ stopPropagation()
و/یا preventDefault()
میروند. اگر تا به حال با خود فکر کرده اید که "من فقط preventDefault()
امتحان می کنم و اگر جواب نداد stopPropagation()
امتحان می کنم و اگر جواب نداد، هر دو را امتحان می کنم، این مقاله برای شماست! من دقیقاً توضیح خواهم داد که هر روش چه کاری انجام می دهد، چه زمانی باید از کدام یک استفاده کرد، و نمونه های کاری متنوعی را برای شما ارائه می کنم تا آنها را بررسی کنید. هدف من این است که یک بار برای همیشه به سردرگمی شما پایان دهم.
با این حال، قبل از اینکه خیلی عمیق بشویم، مهم است که به طور مختصر به دو نوع مدیریت رویداد ممکن در جاوا اسکریپت بپردازیم (در همه مرورگرهای مدرن، اینترنت اکسپلورر قبل از نسخه 9 اصلاً از ضبط رویداد پشتیبانی نمی کرد).
سبک های مراسم (گرفتن و حباب زدن)
همه مرورگرهای مدرن از ضبط رویداد پشتیبانی می کنند، اما توسعه دهندگان به ندرت از آن استفاده می کنند. جالب اینجاست که این تنها شکل رویدادی بود که نت اسکیپ در ابتدا از آن پشتیبانی می کرد. بزرگترین رقیب نت اسکیپ، مایکروسافت اینترنت اکسپلورر، به هیچ وجه از ضبط رویداد پشتیبانی نمی کرد، بلکه تنها از سبک دیگری از رویدادها به نام حباب رویداد پشتیبانی می کرد. هنگامی که W3C تشکیل شد، آنها در هر دو سبک رویدادها شایستگی پیدا کردند و اعلام کردند که مرورگرها باید از هر دو، از طریق پارامتر سوم به روش addEventListener
پشتیبانی کنند. در ابتدا، این پارامتر فقط یک بولی بود، اما همه مرورگرهای مدرن از یک شی options
به عنوان پارامتر سوم پشتیبانی میکنند، که میتوانید از آن برای تعیین (در میان موارد دیگر) استفاده کنید که آیا میخواهید از ثبت رویداد استفاده کنید یا نه:
someElement.addEventListener('click', myClickHandler, { capture: true | false });
توجه داشته باشید که شی options
اختیاری است، همچنین ویژگی capture
آن نیز اختیاری است. اگر یکی از آنها حذف شود، مقدار پیشفرض برای capture
false
است، به این معنی که از حباب رویداد استفاده میشود.
ثبت رویداد
اگر مدیریت رویداد شما "در مرحله ضبط گوش می دهد" به چه معناست؟ برای درک این موضوع، باید بدانیم که رویدادها چگونه منشا می گیرند و چگونه سفر می کنند. موارد زیر در مورد همه رویدادها صادق است، حتی اگر شما به عنوان توسعه دهنده، از آن استفاده نکنید، به آن اهمیت ندهید یا به آن فکر نکنید.
همه رویدادها از پنجره شروع می شوند و ابتدا مرحله ضبط را طی می کنند. این بدان معنی است که هنگامی که یک رویداد ارسال می شود، پنجره را شروع می کند و ابتدا به سمت عنصر هدف خود به سمت پایین حرکت می کند. این اتفاق می افتد حتی اگر شما فقط در مرحله حباب گوش می دهید. مثال زیر نشانه گذاری و جاوا اسکریپت را در نظر بگیرید:
<html>
<body>
<div id="A">
<div id="B">
<div id="C"></div>
</div>
</div>
</body>
</html>
document.getElementById('C').addEventListener(
'click',
function (e) {
console.log('#C was clicked');
},
true,
);
هنگامی که کاربر روی عنصر #C
کلیک می کند، رویدادی که از window
منشا می گیرد، ارسال می شود. سپس این رویداد به صورت زیر در میان فرزندان خود منتشر می شود:
window
=> document
=> <html>
=> <body>
=> و به همین ترتیب، تا زمانی که به هدف برسد.
فرقی نمی کند که هیچ چیزی برای یک رویداد کلیک در window
یا document
یا عنصر <html>
یا عنصر <body>
(یا هر عنصر دیگری در راه رسیدن به هدف خود) گوش نمی دهد. یک رویداد هنوز از window
سرچشمه می گیرد و سفر خود را همانطور که توضیح داده شد آغاز می کند.
در مثال ما، رویداد کلیک سپس (این یک کلمه مهم است، زیرا مستقیماً به نحوه کار متد stopPropagation()
مربوط میشود و بعداً در این سند توضیح داده خواهد شد) از window
به عنصر هدف خود (در این مورد، #C
) از طریق هر عنصر بین window
و #C
منتشر میشود.
این بدان معناست که رویداد کلیک از window
شروع می شود و مرورگر سوالات زیر را می پرسد:
"آیا چیزی برای یک رویداد کلیک روی window
در مرحله تصویربرداری گوش می دهد؟" در این صورت، کنترل کننده های مناسب رویداد شلیک می کنند. در مثال ما، هیچ چیز وجود ندارد، بنابراین هیچ کنترل کننده ای شلیک نمی کند.
سپس، رویداد در document
منتشر میشود و مرورگر میپرسد: "آیا چیزی برای یک رویداد کلیک روی document
در مرحله ضبط کردن گوش میدهد؟" در این صورت، کنترل کننده های مناسب رویداد شلیک می کنند.
در مرحله بعد، رویداد به عنصر <html>
منتشر میشود و مرورگر میپرسد: "آیا چیزی برای یک کلیک روی عنصر <html>
در مرحله ضبط شنیده میشود؟" در این صورت، کنترل کننده های مناسب رویداد شلیک می کنند.
در مرحله بعد، رویداد به عنصر <body>
انتشار مییابد و مرورگر میپرسد: "آیا چیزی برای یک رویداد کلیک روی عنصر <body>
در مرحله تصویربرداری گوش میدهد؟" در این صورت، کنترل کننده های مناسب رویداد شلیک می کنند.
بعد، رویداد به عنصر #A
منتشر می شود. دوباره، مرورگر میپرسد: «آیا چیزی برای یک رویداد کلیک روی #A
در مرحله ضبط شنیده میشود و اگر چنین است، کنترلکنندههای رویداد مناسب فعال میشوند.
بعد، رویداد به عنصر #B
منتشر می شود (و همان سوال پرسیده می شود).
در نهایت، رویداد به هدف خود میرسد و مرورگر میپرسد: "آیا چیزی برای یک رویداد کلیک روی عنصر #C
در مرحله تصویربرداری گوش میدهد؟" پاسخ این بار "بله!" این دوره کوتاه زمانی که رویداد در هدف قرار دارد، به عنوان "فاز هدف" شناخته می شود. در این مرحله، کنترل کننده رویداد فعال می شود، مرورگر console.log "#C کلیک شد" را انجام می دهد و سپس کارمان تمام می شود، درست است؟ اشتباه! ما اصلاً تمام نشده ایم. این روند ادامه دارد، اما اکنون به مرحله حباب تغییر می کند.
حباب رویداد
مرورگر می پرسد:
"آیا چیزی برای یک رویداد کلیک روی #C
در مرحله حباب گوش دادن است؟" به اینجا دقت کنید گوش دادن به کلیک ها (یا هر نوع رویداد) در هر دو مرحله ضبط و حباب کاملاً امکان پذیر است. و اگر کنترلکنندههای رویداد را در هر دو فاز سیمکشی کرده باشید (مثلاً با دو بار فراخوانی .addEventListener()
، یک بار با capture = true
و یک بار با capture = false
)، بله، هر دو کنترل کننده رویداد کاملاً برای یک عنصر فعال میشوند. اما توجه به این نکته نیز مهم است که آنها در فازهای مختلف شلیک می کنند (یکی در فاز گرفتن و دیگری در فاز حباب).
در مرحله بعد، رویداد (به طور معمول به عنوان "حباب" بیان می شود زیرا به نظر می رسد که رویداد در حال حرکت "بالا" درخت DOM است) به عنصر والد خود، #B
، و مرورگر می پرسد: "آیا چیزی برای رویدادهای کلیک روی #B
در مرحله حباب کردن گوش می دهد؟" در مثال ما، هیچ چیز وجود ندارد، بنابراین هیچ کنترل کننده ای شلیک نمی کند.
بعد، رویداد به #A
تبدیل میشود و مرورگر میپرسد: "آیا چیزی برای رویدادهای کلیک روی #A
در مرحله حبابسازی گوش میدهد؟"
در مرحله بعد، رویداد به <body>
تبدیل میشود: "آیا چیزی برای رویدادهای کلیک روی عنصر <body>
در مرحله حبابسازی گوش میدهد؟"
بعد، عنصر <html>
: "آیا چیزی برای رویدادهای کلیک بر روی عنصر <html>
در مرحله حباب گوش می دهد؟
بعد، document
: "آیا چیزی برای رویدادهای کلیک روی document
در مرحله حباب گوش دادن است؟"
در نهایت، window
: "آیا چیزی به رویدادهای کلیک روی پنجره در مرحله حباب گوش می دهد؟"
اوه! این یک سفر طولانی بود، و رویداد ما احتمالاً تا به حال بسیار خسته شده است، اما باور کنید یا نه، این سفری است که هر رویدادی طی می کند! اغلب اوقات، این هرگز مورد توجه قرار نمی گیرد زیرا توسعه دهندگان معمولاً فقط به یک مرحله رویداد یا مرحله دیگر علاقه مند هستند (و معمولاً مرحله حباب است).
ارزش آن را دارد که مدتی را صرف بازی با ضبط رویداد و حباب کردن رویداد کنید و برخی از یادداشتها را در کنسول ثبت کنید. دیدن مسیری که یک رویداد طی می کند بسیار روشنگر است. در اینجا یک مثال است که به هر عنصر در هر دو فاز گوش می دهد.
<html>
<body>
<div id="A">
<div id="B">
<div id="C"></div>
</div>
</div>
</body>
</html>
document.addEventListener(
'click',
function (e) {
console.log('click on document in capturing phase');
},
true,
);
// document.documentElement == <html>
document.documentElement.addEventListener(
'click',
function (e) {
console.log('click on <html> in capturing phase');
},
true,
);
document.body.addEventListener(
'click',
function (e) {
console.log('click on <body> in capturing phase');
},
true,
);
document.getElementById('A').addEventListener(
'click',
function (e) {
console.log('click on #A in capturing phase');
},
true,
);
document.getElementById('B').addEventListener(
'click',
function (e) {
console.log('click on #B in capturing phase');
},
true,
);
document.getElementById('C').addEventListener(
'click',
function (e) {
console.log('click on #C in capturing phase');
},
true,
);
document.addEventListener(
'click',
function (e) {
console.log('click on document in bubbling phase');
},
false,
);
// document.documentElement == <html>
document.documentElement.addEventListener(
'click',
function (e) {
console.log('click on <html> in bubbling phase');
},
false,
);
document.body.addEventListener(
'click',
function (e) {
console.log('click on <body> in bubbling phase');
},
false,
);
document.getElementById('A').addEventListener(
'click',
function (e) {
console.log('click on #A in bubbling phase');
},
false,
);
document.getElementById('B').addEventListener(
'click',
function (e) {
console.log('click on #B in bubbling phase');
},
false,
);
document.getElementById('C').addEventListener(
'click',
function (e) {
console.log('click on #C in bubbling phase');
},
false,
);
خروجی کنسول به عنصری که روی آن کلیک می کنید بستگی دارد. اگر بخواهید روی «عمیقترین» عنصر در درخت DOM (عنصر #C
) کلیک کنید، تک تک این کنترلکنندههای رویداد را مشاهده خواهید کرد. با کمی استایل CSS برای اینکه مشخص شود کدام عنصر کدام است، در اینجا عنصر خروجی کنسول #C
است (همچنین با یک اسکرین شات):
"click on document in capturing phase"
"click on <html> in capturing phase"
"click on <body> in capturing phase"
"click on #A in capturing phase"
"click on #B in capturing phase"
"click on #C in capturing phase"
"click on #C in bubbling phase"
"click on #B in bubbling phase"
"click on #A in bubbling phase"
"click on <body> in bubbling phase"
"click on <html> in bubbling phase"
"click on document in bubbling phase"
شما می توانید به صورت تعاملی با این در دمو زنده زیر بازی کنید. روی عنصر #C
کلیک کنید و خروجی کنسول را مشاهده کنید.
event.stopPropagation()
با درک این موضوع که رویدادها از کجا منشأ می گیرند و چگونه آنها را در DOM در مرحله ضبط و حباب حرکت می کنند (یعنی انتشار می دهند)، اکنون می توانیم توجه خود را به event.stopPropagation()
معطوف کنیم.
متد stopPropagation()
را می توان در (بیشتر) رویدادهای DOM بومی فراخوانی کرد. من میگویم "بیشترین" زیرا چند مورد هستند که فراخوانی این متد هیچ کاری را انجام نمیدهد (زیرا رویداد برای شروع پخش نمیشود). رویدادهایی مانند focus
، blur
، load
، scroll
و چند مورد دیگر در این دسته قرار میگیرند. شما می توانید stopPropagation()
را فراخوانی کنید، اما هیچ اتفاق جالبی نمی افتد، زیرا این رویدادها منتشر نمی شوند.
اما stopPropagation
چه می کند؟
تقریباً همان چیزی را که می گوید انجام می دهد. هنگامی که شما آن را فراخوانی می کنید، از آن نقطه، رویداد انتشار به هر عنصری که در غیر این صورت به آن سفر می کرد، متوقف می شود. این در هر دو جهت (گرفتن و حباب زدن) صادق است. بنابراین اگر stopPropagation()
در هر نقطه ای از فاز گرفتن فراخوانی کنید، رویداد هرگز به فاز هدف یا فاز حباب نمی رسد. اگر آن را در مرحله حباب صدا بزنید، قبلاً مرحله ضبط را پشت سر گذاشته است، اما از نقطه ای که شما آن را فراخوانی کرده اید «حباب کردن» متوقف می شود.
با بازگشت به همان نشانهگذاری مثال خود، فکر میکنید چه اتفاقی میافتد، اگر stopPropagation()
را در فاز گرفتن در عنصر #B
فراخوانی کنیم؟
به خروجی زیر منجر می شود:
"click on document in capturing phase"
"click on <html> in capturing phase"
"click on <body> in capturing phase"
"click on #A in capturing phase"
"click on #B in capturing phase"
شما می توانید به صورت تعاملی با این در دمو زنده زیر بازی کنید. روی عنصر #C
در دمو زنده کلیک کنید و خروجی کنسول را مشاهده کنید.
در مورد توقف انتشار در #A
در مرحله حباب زدن چطور؟ که منجر به خروجی زیر می شود:
"click on document in capturing phase"
"click on <html> in capturing phase"
"click on <body> in capturing phase"
"click on #A in capturing phase"
"click on #B in capturing phase"
"click on #C in capturing phase"
"click on #C in bubbling phase"
"click on #B in bubbling phase"
"click on #A in bubbling phase"
شما می توانید به صورت تعاملی با این در دمو زنده زیر بازی کنید. روی عنصر #C
در دمو زنده کلیک کنید و خروجی کنسول را مشاهده کنید.
یکی دیگه فقط برای سرگرمی اگر در فاز هدف برای #C
stopPropagation()
را فراخوانی کنیم، چه اتفاقی میافتد؟ به یاد بیاورید که "فاز هدف" نامی است که به دوره زمانی داده می شود که رویداد در هدف خود قرار دارد. به خروجی زیر منجر می شود:
"click on document in capturing phase"
"click on <html> in capturing phase"
"click on <body> in capturing phase"
"click on #A in capturing phase"
"click on #B in capturing phase"
"click on #C in capturing phase"
توجه داشته باشید که کنترل کننده رویداد برای #C
که در آن Log "در مرحله ثبت بر روی #C کلیک کنید" همچنان اجرا می شود، اما یکی که در آن Log می کنیم "در مرحله حباب بر روی #C کلیک کنید" اجرا نمی شود. این باید کاملاً منطقی باشد. ما stopPropagation()
از اولی فراخوانی کردیم، بنابراین این نقطه ای است که در آن انتشار رویداد متوقف می شود.
شما می توانید به صورت تعاملی با این در دمو زنده زیر بازی کنید. روی عنصر #C
در دمو زنده کلیک کنید و خروجی کنسول را مشاهده کنید.
در هر یک از این دموهای زنده، من شما را تشویق می کنم که در اطراف بازی کنید. فقط روی عنصر #A
یا فقط عنصر body
کلیک کنید. سعی کنید پیش بینی کنید چه اتفاقی خواهد افتاد و سپس مشاهده کنید که آیا درست می گویید یا خیر. در این مرحله، شما باید بتوانید کاملاً دقیق پیش بینی کنید.
event.stopImmediatePropagation()
این روش عجیب و پرکاربرد چیست؟ این روش مشابه stopPropagation
است، اما به جای جلوگیری از سفر یک رویداد به فرزندان (گرفتن) یا اجداد (حباب زدن)، این روش تنها زمانی اعمال میشود که بیش از یک کنترل کننده رویداد را به یک عنصر متصل کنید. از آنجایی که addEventListener()
از یک سبک چندپخشی رویداد پشتیبانی میکند، میتوان یک کنترلر رویداد را بیش از یک بار به یک عنصر متصل کرد. وقتی این اتفاق میافتد، (در اکثر مرورگرها)، کنترلکنندههای رویداد به ترتیبی که سیمکشی شدهاند اجرا میشوند. فراخوانی stopImmediatePropagation()
از شلیک هر کنترل کننده بعدی جلوگیری می کند. به مثال زیر توجه کنید:
<html>
<body>
<div id="A">I am the #A element</div>
</body>
</html>
document.getElementById('A').addEventListener(
'click',
function (e) {
console.log('When #A is clicked, I shall run first!');
},
false,
);
document.getElementById('A').addEventListener(
'click',
function (e) {
console.log('When #A is clicked, I shall run second!');
e.stopImmediatePropagation();
},
false,
);
document.getElementById('A').addEventListener(
'click',
function (e) {
console.log('When #A is clicked, I would have run third, if not for stopImmediatePropagation');
},
false,
);
مثال بالا به خروجی کنسول زیر منجر می شود:
"When #A is clicked, I shall run first!"
"When #A is clicked, I shall run second!"
توجه داشته باشید که سومین کنترل کننده رویداد هرگز اجرا نمی شود، زیرا دومین کنترل کننده رویداد e.stopImmediatePropagation()
را فراخوانی می کند. اگر به جای آن e.stopPropagation()
فراخوانی می کردیم، سومین کنترل کننده همچنان اجرا می شد.
event.preventDefault()
اگر stopPropagation()
مانع از حرکت یک رویداد "به سمت پایین" (گرفتن) یا "بالا" (حباب زدن) شود، پس، preventDefault()
چه کاری انجام می دهد؟ به نظر می رسد که کاری مشابه انجام می دهد. آیا آن را انجام می دهد؟
نه واقعا. در حالی که این دو اغلب اشتباه گرفته می شوند، اما در واقع ارتباط زیادی با یکدیگر ندارند. هنگامی که preventDefault()
مشاهده کردید، در سر خود کلمه "action" را اضافه کنید. به "جلوگیری از اقدام پیش فرض" فکر کنید.
و اقدام پیش فرضی که ممکن است بپرسید چیست؟ متأسفانه، پاسخ به آن کاملاً واضح نیست زیرا به شدت به ترکیب عنصر + رویداد مورد نظر وابسته است. و برای گیج شدن بیشتر، گاهی اوقات هیچ اقدام پیش فرضی وجود ندارد!
بیایید با یک مثال بسیار ساده برای درک شروع کنیم. وقتی روی یک لینک در یک صفحه وب کلیک می کنید، انتظار دارید چه اتفاقی بیفتد؟ بدیهی است که انتظار دارید مرورگر به URL مشخص شده توسط آن پیوند حرکت کند. در این حالت، عنصر یک تگ لنگر و رویداد یک رویداد کلیک است. این ترکیب ( <a>
+ click
) دارای یک "عمل پیش فرض" برای پیمایش به href پیوند است. اگر بخواهید از انجام آن عملکرد پیش فرض مرورگر جلوگیری کنید ، چه؟ یعنی فرض کنید می خواهید از پیمایش مرورگر به URL مشخص شده توسط ویژگی href
عنصر <a>
جلوگیری کنید؟ این کاری است که preventDefault()
برای شما انجام خواهد داد. این مثال را در نظر بگیرید:
<a id="avett" href="https://www.theavettbrothers.com/welcome">The Avett Brothers</a>
document.getElementById('avett').addEventListener(
'click',
function (e) {
e.preventDefault();
console.log('Maybe we should just play some of their music right here instead?');
},
false,
);
شما می توانید به صورت تعاملی با این در دمو زنده زیر بازی کنید. روی پیوند The Avett Brothers کلیک کنید و خروجی کنسول (و این واقعیت که شما به وب سایت Avett Brothers هدایت نشده اید) را مشاهده کنید.
به طور معمول، کلیک کردن بر روی پیوند با عنوان The Avett Brothers منجر به مرور به www.theavettbrothers.com
می شود. با این حال، در این مورد، ما یک کنترل کننده رویداد کلیک را به عنصر <a>
متصل کردهایم و مشخص کردهایم که از عملکرد پیشفرض باید جلوگیری شود. بنابراین، زمانی که کاربر روی این پیوند کلیک میکند، به هیچجا هدایت نمیشود، و در عوض کنسول به سادگی وارد سیستم میشود: «شاید ما فقط برخی از موسیقیهای او را همینجا پخش کنیم؟»
چه ترکیبهای عنصر/رویداد دیگری به شما امکان میدهد از عملکرد پیشفرض جلوگیری کنید؟ من نمی توانم همه آنها را فهرست کنم، و گاهی اوقات برای دیدن باید فقط آزمایش کنید. اما به طور خلاصه به چند مورد اشاره می کنیم:
عنصر
<form>
+ رویداد "submit":preventDefault()
برای این ترکیب از ارسال فرم جلوگیری می کند. اگر میخواهید اعتبارسنجی را انجام دهید و در صورت عدم موفقیت، میتوانید به طور مشروط با preventDefault تماس بگیرید تا فرم ارسال نشود.عنصر
<a>
+ رویداد "click":preventDefault()
برای این ترکیب از پیمایش مرورگر به URL مشخص شده در ویژگی href عنصر<a>
جلوگیری می کند.رویداد
document
+ "mousewheel":preventDefault()
برای این ترکیب از پیمایش صفحه با چرخ ماوس جلوگیری می کند (هرچند پیمایش با صفحه کلید همچنان کار می کند).
↜ این نیاز به فراخوانیaddEventListener()
با{ passive: false }
دارد .رویداد
document
+ "keydown":preventDefault()
برای این ترکیب کشنده است. این صفحه را تا حد زیادی بی فایده می کند و از اسکرول صفحه کلید، زبانه ها و برجسته شدن صفحه کلید جلوگیری می کند.document
+ رویداد "mousedown":preventDefault()
برای این ترکیب از برجسته کردن متن با ماوس و هر عمل "پیشفرض" دیگری که با پایین ماوس فراخوانی میشود، جلوگیری میکند.عنصر
<input>
+ رویداد "keypress":preventDefault()
برای این ترکیب از رسیدن کاراکترهای تایپ شده توسط کاربر به عنصر ورودی جلوگیری می کند (اما این کار را نکنید، به ندرت، اگر هرگز، دلیل معتبری برای آن وجود دارد).رویداد
document
+ "contextmenu":preventDefault()
برای این ترکیب از نمایش منوی زمینه بومی مرورگر هنگام کلیک راست یا فشار طولانی کاربر (یا هر روش دیگری که ممکن است منوی زمینه ظاهر شود) جلوگیری می کند.
این به هیچ وجه لیست جامعی نیست، اما امیدواریم که به شما ایده خوبی درباره نحوه استفاده preventDefault()
بدهد.
یک شوخی کاربردی جالب؟
چه اتفاقی می افتد اگر stopPropagation()
و preventDefault()
در مرحله capturing، با شروع از سند، متوقف کنید؟ شوخ طبعی به وجود می آید! قطعه کد زیر هر صفحه وب را تقریباً کاملاً بی فایده نشان می دهد:
function preventEverything(e) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
}
document.addEventListener('click', preventEverything, true);
document.addEventListener('keydown', preventEverything, true);
document.addEventListener('mousedown', preventEverything, true);
document.addEventListener('contextmenu', preventEverything, true);
document.addEventListener('mousewheel', preventEverything, { capture: true, passive: false });
من واقعاً نمیدانم چرا هرگز میخواهید این کار را انجام دهید (به جز اینکه ممکن است با کسی شوخی کنید)، اما فکر کردن به آنچه در اینجا اتفاق میافتد و درک اینکه چرا موقعیتی را ایجاد میکند مفید است.
همه رویدادها از window
سرچشمه میگیرند، بنابراین در این قطعه، ما متوقف میشویم، در مسیرهای آنها مردهاند، همه click
، keydown
، mousedown
، contextmenu
، و رویدادهای mousewheel
از رسیدن به عناصری که ممکن است به آنها گوش میدهند میرسیم. ما همچنین stopImmediatePropagation
را می نامیم تا هر کنترل کننده ای که بعد از این به سند متصل شود نیز خنثی شود.
توجه داشته باشید که stopPropagation()
و stopImmediatePropagation()
چیزی نیستند (حداقل نه بیشتر) که صفحه را بی فایده می کنند. آنها به سادگی از رسیدن رویدادها به جایی که در غیر این صورت میرفتند جلوگیری میکنند.
اما ما همچنین preventDefault()
فراخوانی می کنیم، که به یاد می آورید از عمل پیش فرض جلوگیری می کند. بنابراین هر گونه عملکرد پیش فرض (مانند پیمایش چرخ ماوس، پیمایش صفحه کلید یا برجسته کردن یا تب، کلیک کردن روی پیوند، نمایش منوی زمینه، و غیره) همگی جلوگیری می شود، بنابراین صفحه در حالت نسبتاً بی فایده باقی می ماند.
دموهای زنده
برای بررسی مجدد همه نمونههای این مقاله در یک مکان، نسخه نمایشی تعبیهشده زیر را بررسی کنید.
قدردانی
تصویر قهرمان توسط تام ویلسون در Unsplash .
، preventDefault
و stopPropagation
: چه زمانی باید از کدام روش استفاده کرد و دقیقاً چه کاری انجام می دهد.
Event.stopPropagation() و Event.preventDefault()
مدیریت رویداد جاوا اسکریپت اغلب ساده است. این امر به ویژه هنگامی که با ساختار HTML ساده (نسبتاً مسطح) سروکار داریم، صادق است. وقتی رویدادها از طریق سلسله مراتبی از عناصر در حال حرکت (یا انتشار) هستند، همه چیز کمی بیشتر درگیر می شود. این معمولاً زمانی است که توسعهدهندگان برای حل مشکلاتی که با آن مواجه هستند به سراغ stopPropagation()
و/یا preventDefault()
میروند. اگر تا به حال با خود فکر کرده اید که "من فقط preventDefault()
امتحان می کنم و اگر جواب نداد stopPropagation()
امتحان می کنم و اگر جواب نداد، هر دو را امتحان می کنم، این مقاله برای شماست! من دقیقاً توضیح خواهم داد که هر روش چه کاری انجام می دهد، چه زمانی باید از کدام یک استفاده کرد، و نمونه های کاری متنوعی را برای شما ارائه می کنم تا آنها را بررسی کنید. هدف من این است که یک بار برای همیشه به سردرگمی شما پایان دهم.
با این حال، قبل از اینکه خیلی عمیق بشویم، مهم است که به طور مختصر به دو نوع مدیریت رویداد ممکن در جاوا اسکریپت بپردازیم (در همه مرورگرهای مدرن، اینترنت اکسپلورر قبل از نسخه 9 اصلاً از ضبط رویداد پشتیبانی نمی کرد).
سبک های مراسم (گرفتن و حباب زدن)
همه مرورگرهای مدرن از ضبط رویداد پشتیبانی می کنند، اما توسعه دهندگان به ندرت از آن استفاده می کنند. جالب اینجاست که این تنها شکل رویدادی بود که نت اسکیپ در ابتدا از آن پشتیبانی می کرد. بزرگترین رقیب نت اسکیپ، مایکروسافت اینترنت اکسپلورر، به هیچ وجه از ضبط رویداد پشتیبانی نمی کرد، بلکه تنها از سبک دیگری از رویدادها به نام حباب رویداد پشتیبانی می کرد. هنگامی که W3C تشکیل شد، آنها در هر دو سبک رویدادها شایستگی پیدا کردند و اعلام کردند که مرورگرها باید از هر دو، از طریق پارامتر سوم به روش addEventListener
پشتیبانی کنند. در ابتدا، این پارامتر فقط یک بولی بود، اما همه مرورگرهای مدرن از یک شی options
به عنوان پارامتر سوم پشتیبانی میکنند، که میتوانید از آن برای تعیین (در میان موارد دیگر) استفاده کنید که آیا میخواهید از ثبت رویداد استفاده کنید یا نه:
someElement.addEventListener('click', myClickHandler, { capture: true | false });
توجه داشته باشید که شی options
اختیاری است، همچنین ویژگی capture
آن نیز اختیاری است. اگر یکی از آنها حذف شود، مقدار پیشفرض برای capture
false
است، به این معنی که از حباب رویداد استفاده میشود.
ثبت رویداد
اگر مدیریت رویداد شما "در مرحله ضبط گوش می دهد" به چه معناست؟ برای درک این موضوع، باید بدانیم که رویدادها چگونه منشا می گیرند و چگونه سفر می کنند. موارد زیر در مورد همه رویدادها صادق است، حتی اگر شما به عنوان توسعه دهنده، از آن استفاده نکنید، به آن اهمیت ندهید یا به آن فکر نکنید.
همه رویدادها از پنجره شروع می شوند و ابتدا مرحله ضبط را طی می کنند. این بدان معنی است که هنگامی که یک رویداد ارسال می شود، پنجره را شروع می کند و ابتدا به سمت عنصر هدف خود به سمت پایین حرکت می کند. این اتفاق می افتد حتی اگر شما فقط در مرحله حباب گوش می دهید. مثال زیر نشانه گذاری و جاوا اسکریپت را در نظر بگیرید:
<html>
<body>
<div id="A">
<div id="B">
<div id="C"></div>
</div>
</div>
</body>
</html>
document.getElementById('C').addEventListener(
'click',
function (e) {
console.log('#C was clicked');
},
true,
);
هنگامی که کاربر روی عنصر #C
کلیک می کند، رویدادی که از window
منشا می گیرد، ارسال می شود. سپس این رویداد به صورت زیر در میان فرزندان خود منتشر می شود:
window
=> document
=> <html>
=> <body>
=> و به همین ترتیب، تا زمانی که به هدف برسد.
فرقی نمی کند که هیچ چیزی برای یک رویداد کلیک در window
یا document
یا عنصر <html>
یا عنصر <body>
(یا هر عنصر دیگری در راه رسیدن به هدف خود) گوش نمی دهد. یک رویداد هنوز از window
سرچشمه می گیرد و سفر خود را همانطور که توضیح داده شد آغاز می کند.
در مثال ما، رویداد کلیک سپس (این یک کلمه مهم است، زیرا مستقیماً به نحوه کار متد stopPropagation()
مربوط میشود و بعداً در این سند توضیح داده خواهد شد) از window
به عنصر هدف خود (در این مورد، #C
) از طریق هر عنصر بین window
و #C
منتشر میشود.
این بدان معناست که رویداد کلیک از window
شروع می شود و مرورگر سوالات زیر را می پرسد:
"آیا چیزی برای یک رویداد کلیک روی window
در مرحله تصویربرداری گوش می دهد؟" در این صورت، کنترل کننده های مناسب رویداد شلیک می کنند. در مثال ما، هیچ چیز وجود ندارد، بنابراین هیچ کنترل کننده ای شلیک نمی کند.
سپس، رویداد در document
منتشر میشود و مرورگر میپرسد: "آیا چیزی برای یک رویداد کلیک روی document
در مرحله ضبط کردن گوش میدهد؟" در این صورت، کنترل کننده های مناسب رویداد شلیک می کنند.
در مرحله بعد، رویداد به عنصر <html>
منتشر میشود و مرورگر میپرسد: "آیا چیزی برای یک کلیک روی عنصر <html>
در مرحله ضبط شنیده میشود؟" در این صورت، گردانندگان مناسب رویداد شلیک خواهند کرد.
در مرحله بعد، رویداد به عنصر <body>
انتشار مییابد و مرورگر میپرسد: "آیا چیزی برای یک رویداد کلیک روی عنصر <body>
در مرحله تصویربرداری گوش میدهد؟" در این صورت، گردانندگان مناسب رویداد شلیک خواهند کرد.
بعد، رویداد به عنصر #A
منتشر می شود. دوباره، مرورگر میپرسد: «آیا چیزی برای یک رویداد کلیک روی #A
در مرحله ضبط شنیده میشود و اگر چنین است، کنترلکنندههای رویداد مناسب فعال میشوند.
بعد، رویداد به عنصر #B
منتشر می شود (و همان سوال پرسیده می شود).
در نهایت، رویداد به هدف خود میرسد و مرورگر میپرسد: "آیا چیزی برای یک رویداد کلیک روی عنصر #C
در مرحله تصویربرداری گوش میدهد؟" پاسخ این بار "بله!" این دوره کوتاه زمانی که رویداد در هدف قرار دارد، به عنوان "فاز هدف" شناخته می شود. در این مرحله، کنترل کننده رویداد فعال می شود، مرورگر console.log "#C کلیک شد" را انجام می دهد و سپس کارمان تمام می شود، درست است؟ اشتباه! ما اصلاً تمام نشده ایم. این روند ادامه دارد، اما اکنون به مرحله حباب تغییر می کند.
حباب رویداد
مرورگر می پرسد:
"آیا چیزی برای یک رویداد کلیک روی #C
در مرحله حباب گوش دادن است؟" به اینجا دقت کنید گوش دادن به کلیک ها (یا هر نوع رویداد) در هر دو مرحله ضبط و حباب کاملاً امکان پذیر است. و اگر کنترلکنندههای رویداد را در هر دو فاز سیمکشی کرده باشید (مثلاً با دو بار فراخوانی .addEventListener()
، یک بار با capture = true
و یک بار با capture = false
)، بله، هر دو کنترل کننده رویداد کاملاً برای یک عنصر فعال میشوند. اما توجه به این نکته نیز مهم است که آنها در فازهای مختلف شلیک می کنند (یکی در فاز گرفتن و دیگری در فاز حباب).
در مرحله بعد، رویداد (به طور معمول به عنوان "حباب" بیان می شود زیرا به نظر می رسد که رویداد در حال حرکت "بالا" درخت DOM است) به عنصر والد خود، #B
، و مرورگر می پرسد: "آیا چیزی برای رویدادهای کلیک روی #B
در مرحله حباب کردن گوش می دهد؟" در مثال ما، هیچ چیز وجود ندارد، بنابراین هیچ کنترل کننده ای شلیک نمی کند.
بعد، رویداد به #A
تبدیل میشود و مرورگر میپرسد: "آیا چیزی برای رویدادهای کلیک روی #A
در مرحله حبابسازی گوش میدهد؟"
در مرحله بعد، رویداد به <body>
تبدیل میشود: "آیا چیزی برای رویدادهای کلیک روی عنصر <body>
در مرحله حبابسازی گوش میدهد؟"
بعد، عنصر <html>
: "آیا چیزی برای رویدادهای کلیک بر روی عنصر <html>
در مرحله حباب گوش می دهد؟
بعد، document
: "آیا چیزی برای رویدادهای کلیک روی document
در مرحله حباب گوش دادن است؟"
در نهایت، window
: "آیا چیزی به رویدادهای کلیک روی پنجره در مرحله حباب گوش می دهد؟"
اوه! این یک سفر طولانی بود، و رویداد ما احتمالاً تا به حال بسیار خسته شده است، اما باور کنید یا نه، این سفری است که هر رویدادی طی می کند! اغلب اوقات، این هرگز مورد توجه قرار نمی گیرد زیرا توسعه دهندگان معمولاً فقط به یک مرحله رویداد یا مرحله دیگر علاقه مند هستند (و معمولاً مرحله حباب است).
ارزش آن را دارد که مدتی را صرف بازی با ضبط رویداد و حباب کردن رویداد کنید و برخی از یادداشتها را در کنسول ثبت کنید. دیدن مسیری که یک رویداد طی می کند بسیار روشنگر است. در اینجا یک مثال است که به هر عنصر در هر دو فاز گوش می دهد.
<html>
<body>
<div id="A">
<div id="B">
<div id="C"></div>
</div>
</div>
</body>
</html>
document.addEventListener(
'click',
function (e) {
console.log('click on document in capturing phase');
},
true,
);
// document.documentElement == <html>
document.documentElement.addEventListener(
'click',
function (e) {
console.log('click on <html> in capturing phase');
},
true,
);
document.body.addEventListener(
'click',
function (e) {
console.log('click on <body> in capturing phase');
},
true,
);
document.getElementById('A').addEventListener(
'click',
function (e) {
console.log('click on #A in capturing phase');
},
true,
);
document.getElementById('B').addEventListener(
'click',
function (e) {
console.log('click on #B in capturing phase');
},
true,
);
document.getElementById('C').addEventListener(
'click',
function (e) {
console.log('click on #C in capturing phase');
},
true,
);
document.addEventListener(
'click',
function (e) {
console.log('click on document in bubbling phase');
},
false,
);
// document.documentElement == <html>
document.documentElement.addEventListener(
'click',
function (e) {
console.log('click on <html> in bubbling phase');
},
false,
);
document.body.addEventListener(
'click',
function (e) {
console.log('click on <body> in bubbling phase');
},
false,
);
document.getElementById('A').addEventListener(
'click',
function (e) {
console.log('click on #A in bubbling phase');
},
false,
);
document.getElementById('B').addEventListener(
'click',
function (e) {
console.log('click on #B in bubbling phase');
},
false,
);
document.getElementById('C').addEventListener(
'click',
function (e) {
console.log('click on #C in bubbling phase');
},
false,
);
خروجی کنسول به عنصری که روی آن کلیک می کنید بستگی دارد. اگر بخواهید روی «عمیقترین» عنصر در درخت DOM (عنصر #C
) کلیک کنید، تک تک این کنترلکنندههای رویداد را مشاهده خواهید کرد. با کمی استایل CSS برای اینکه مشخص شود کدام عنصر کدام است، در اینجا عنصر خروجی کنسول #C
است (همچنین با یک اسکرین شات):
"click on document in capturing phase"
"click on <html> in capturing phase"
"click on <body> in capturing phase"
"click on #A in capturing phase"
"click on #B in capturing phase"
"click on #C in capturing phase"
"click on #C in bubbling phase"
"click on #B in bubbling phase"
"click on #A in bubbling phase"
"click on <body> in bubbling phase"
"click on <html> in bubbling phase"
"click on document in bubbling phase"
شما می توانید به صورت تعاملی با این در دمو زنده زیر بازی کنید. روی عنصر #C
کلیک کنید و خروجی کنسول را مشاهده کنید.
event.stopPropagation()
با درک این موضوع که رویدادها از کجا منشأ می گیرند و چگونه آنها را در DOM در مرحله ضبط و حباب حرکت می کنند (یعنی انتشار می دهند)، اکنون می توانیم توجه خود را به event.stopPropagation()
معطوف کنیم.
متد stopPropagation()
را می توان در (بیشتر) رویدادهای DOM بومی فراخوانی کرد. من میگویم "بیشترین" زیرا چند مورد هستند که فراخوانی این متد هیچ کاری را انجام نمیدهد (زیرا رویداد برای شروع پخش نمیشود). رویدادهایی مانند focus
، blur
، load
، scroll
و چند مورد دیگر در این دسته قرار میگیرند. شما می توانید stopPropagation()
را فراخوانی کنید، اما هیچ اتفاق جالبی نمی افتد، زیرا این رویدادها منتشر نمی شوند.
اما stopPropagation
چه می کند؟
تقریباً همان چیزی را که می گوید انجام می دهد. هنگامی که شما آن را فراخوانی می کنید، از آن نقطه، رویداد انتشار به هر عنصری که در غیر این صورت به آن سفر می کرد، متوقف می شود. این در هر دو جهت (گرفتن و حباب زدن) صادق است. بنابراین اگر stopPropagation()
در هر نقطه ای از فاز گرفتن فراخوانی کنید، رویداد هرگز به فاز هدف یا فاز حباب نمی رسد. اگر آن را در مرحله حباب صدا بزنید، قبلاً مرحله ضبط را پشت سر گذاشته است، اما از نقطه ای که شما آن را فراخوانی کرده اید «حباب کردن» متوقف می شود.
با بازگشت به همان نشانهگذاری مثال خود، فکر میکنید چه اتفاقی میافتد، اگر stopPropagation()
را در فاز گرفتن در عنصر #B
فراخوانی کنیم؟
به خروجی زیر منجر می شود:
"click on document in capturing phase"
"click on <html> in capturing phase"
"click on <body> in capturing phase"
"click on #A in capturing phase"
"click on #B in capturing phase"
شما می توانید به صورت تعاملی با این در دمو زنده زیر بازی کنید. روی عنصر #C
در دمو زنده کلیک کنید و خروجی کنسول را مشاهده کنید.
در مورد توقف انتشار در #A
در مرحله حباب زدن چطور؟ که منجر به خروجی زیر می شود:
"click on document in capturing phase"
"click on <html> in capturing phase"
"click on <body> in capturing phase"
"click on #A in capturing phase"
"click on #B in capturing phase"
"click on #C in capturing phase"
"click on #C in bubbling phase"
"click on #B in bubbling phase"
"click on #A in bubbling phase"
شما می توانید به صورت تعاملی با این در دمو زنده زیر بازی کنید. روی عنصر #C
در دمو زنده کلیک کنید و خروجی کنسول را مشاهده کنید.
یکی دیگه فقط برای سرگرمی اگر در فاز هدف برای #C
stopPropagation()
را فراخوانی کنیم، چه اتفاقی میافتد؟ به یاد بیاورید که "فاز هدف" نامی است که به دوره زمانی داده می شود که رویداد در هدف خود قرار دارد. به خروجی زیر منجر می شود:
"click on document in capturing phase"
"click on <html> in capturing phase"
"click on <body> in capturing phase"
"click on #A in capturing phase"
"click on #B in capturing phase"
"click on #C in capturing phase"
توجه داشته باشید که کنترل کننده رویداد برای #C
که در آن Log "در مرحله ثبت بر روی #C کلیک کنید" همچنان اجرا می شود، اما یکی که در آن Log می کنیم "در مرحله حباب بر روی #C کلیک کنید" اجرا نمی شود. این باید کاملاً منطقی باشد. ما stopPropagation()
از اولی فراخوانی کردیم، بنابراین این نقطه ای است که در آن انتشار رویداد متوقف می شود.
شما می توانید به صورت تعاملی با این در دمو زنده زیر بازی کنید. روی عنصر #C
در دمو زنده کلیک کنید و خروجی کنسول را مشاهده کنید.
در هر یک از این دموهای زنده، من شما را تشویق می کنم که در اطراف بازی کنید. فقط روی عنصر #A
یا فقط عنصر body
کلیک کنید. سعی کنید پیش بینی کنید چه اتفاقی خواهد افتاد و سپس مشاهده کنید که آیا درست می گویید یا خیر. در این مرحله، شما باید بتوانید کاملاً دقیق پیش بینی کنید.
event.stopImmediatePropagation()
این روش عجیب و پرکاربرد چیست؟ این روش مشابه stopPropagation
است، اما به جای جلوگیری از سفر یک رویداد به فرزندان (گرفتن) یا اجداد (حباب زدن)، این روش تنها زمانی اعمال میشود که بیش از یک کنترل کننده رویداد را به یک عنصر متصل کنید. از آنجایی که addEventListener()
از یک سبک چندپخشی رویداد پشتیبانی میکند، میتوان یک کنترلر رویداد را بیش از یک بار به یک عنصر متصل کرد. وقتی این اتفاق میافتد، (در اکثر مرورگرها)، کنترلکنندههای رویداد به ترتیبی که سیمکشی شدهاند اجرا میشوند. فراخوانی stopImmediatePropagation()
از شلیک هر کنترل کننده بعدی جلوگیری می کند. به مثال زیر توجه کنید:
<html>
<body>
<div id="A">I am the #A element</div>
</body>
</html>
document.getElementById('A').addEventListener(
'click',
function (e) {
console.log('When #A is clicked, I shall run first!');
},
false,
);
document.getElementById('A').addEventListener(
'click',
function (e) {
console.log('When #A is clicked, I shall run second!');
e.stopImmediatePropagation();
},
false,
);
document.getElementById('A').addEventListener(
'click',
function (e) {
console.log('When #A is clicked, I would have run third, if not for stopImmediatePropagation');
},
false,
);
مثال بالا به خروجی کنسول زیر منجر می شود:
"When #A is clicked, I shall run first!"
"When #A is clicked, I shall run second!"
توجه داشته باشید که سومین کنترل کننده رویداد هرگز اجرا نمی شود، زیرا دومین کنترل کننده رویداد e.stopImmediatePropagation()
را فراخوانی می کند. اگر به جای آن e.stopPropagation()
فراخوانی می کردیم، سومین کنترل کننده همچنان اجرا می شد.
event.preventDefault()
اگر stopPropagation()
مانع از حرکت یک رویداد "به سمت پایین" (گرفتن) یا "بالا" (حباب زدن) شود، پس، preventDefault()
چه کاری انجام می دهد؟ به نظر می رسد که کاری مشابه انجام می دهد. آیا آن را انجام می دهد؟
نه واقعا. در حالی که این دو اغلب اشتباه گرفته می شوند، اما در واقع ارتباط زیادی با یکدیگر ندارند. هنگامی که preventDefault()
مشاهده کردید، در سر خود کلمه "action" را اضافه کنید. به "جلوگیری از اقدام پیش فرض" فکر کنید.
و اقدام پیش فرضی که ممکن است بپرسید چیست؟ متأسفانه، پاسخ به آن کاملاً واضح نیست زیرا به شدت به ترکیب عنصر + رویداد مورد نظر وابسته است. و برای گیج شدن بیشتر، گاهی اوقات هیچ اقدام پیش فرضی وجود ندارد!
بیایید با یک مثال بسیار ساده برای درک شروع کنیم. وقتی روی یک لینک در یک صفحه وب کلیک می کنید، انتظار دارید چه اتفاقی بیفتد؟ بدیهی است که انتظار دارید مرورگر به URL مشخص شده توسط آن پیوند حرکت کند. در این حالت، عنصر یک تگ لنگر و رویداد یک رویداد کلیک است. این ترکیب ( <a>
+ click
) دارای یک "عمل پیش فرض" برای پیمایش به href پیوند است. اگر بخواهید از انجام آن عملکرد پیش فرض مرورگر جلوگیری کنید ، چه؟ یعنی فرض کنید می خواهید از پیمایش مرورگر به URL مشخص شده توسط ویژگی href
عنصر <a>
جلوگیری کنید؟ این کاری است که preventDefault()
برای شما انجام خواهد داد. این مثال را در نظر بگیرید:
<a id="avett" href="https://www.theavettbrothers.com/welcome">The Avett Brothers</a>
document.getElementById('avett').addEventListener(
'click',
function (e) {
e.preventDefault();
console.log('Maybe we should just play some of their music right here instead?');
},
false,
);
شما می توانید به صورت تعاملی با این در دمو زنده زیر بازی کنید. روی پیوند The Avett Brothers کلیک کنید و خروجی کنسول (و این واقعیت که شما به وب سایت Avett Brothers هدایت نشده اید) را مشاهده کنید.
به طور معمول، کلیک کردن بر روی پیوند با عنوان The Avett Brothers منجر به مرور به www.theavettbrothers.com
می شود. با این حال، در این مورد، ما یک کنترل کننده رویداد کلیک را به عنصر <a>
متصل کردهایم و مشخص کردهایم که از عملکرد پیشفرض باید جلوگیری شود. بنابراین، زمانی که کاربر روی این پیوند کلیک میکند، به هیچجا هدایت نمیشود، و در عوض کنسول به سادگی وارد سیستم میشود: «شاید ما فقط برخی از موسیقیهای او را همینجا پخش کنیم؟»
چه ترکیبهای عنصر/رویداد دیگری به شما امکان میدهد از عملکرد پیشفرض جلوگیری کنید؟ من نمی توانم همه آنها را فهرست کنم، و گاهی اوقات برای دیدن باید فقط آزمایش کنید. اما به طور خلاصه به چند مورد اشاره می کنیم:
عنصر
<form>
+ رویداد "submit":preventDefault()
برای این ترکیب از ارسال فرم جلوگیری می کند. اگر میخواهید اعتبارسنجی را انجام دهید و در صورت عدم موفقیت، میتوانید به طور مشروط با preventDefault تماس بگیرید تا فرم ارسال نشود.عنصر
<a>
+ رویداد "click":preventDefault()
برای این ترکیب از پیمایش مرورگر به URL مشخص شده در ویژگی href عنصر<a>
جلوگیری می کند.document
+ رویداد "Mousewheel" رویداد:preventDefault()
برای این ترکیب مانع از پیمایش صفحه با ماوس می شود (پیمایش با صفحه کلید هنوز هم کار می کند).
↜ این نیاز به فراخوانیaddEventListener()
با{ passive: false }
.document
+ رویداد "keydown" رویداد:preventDefault()
برای این ترکیب کشنده است. این صفحه را تا حد زیادی بی فایده می کند و از پیمایش صفحه کلید ، زبانه و برجسته صفحه کلید جلوگیری می کند.document
+ رویداد "Mousedown":preventDefault()
برای این ترکیب مانع از برجسته کردن متن با ماوس و هر اقدام "پیش فرض" دیگری می شود که شخص با یک ماوس به پایین می رود.<input>
Element + "KeyPress" رویداد:preventDefault()
برای این ترکیب باعث می شود شخصیت های تایپ شده توسط کاربر به عنصر ورودی تایپ شوند (اما این کار را انجام ندهید ؛ به ندرت ، اگر همیشه دلیل معتبری برای آن وجود دارد).document
+ رویداد "ContextMenu":preventDefault()
برای این ترکیب مانع از ظاهر شدن منوی زمینه مرورگر بومی می شود وقتی کاربر راست کلیک می کند یا فشار طولانی (یا هر راه دیگری که ممکن است یک منوی زمینه ظاهر شود).
این یک لیست جامع به هیچ وجه نیست ، اما امیدوارم که ایده خوبی در مورد چگونگی استفاده preventDefault()
باشد.
یک شوخی عملی سرگرم کننده؟
چه اتفاقی می افتد اگر شما در مرحله ضبط ، شروع به کار در مرحله ضبط ، و پیشبرد () و preventDefault()
stopPropagation()
؟ شوخ طبعی به وجود می آید! قطعه کد زیر هر صفحه وب را کاملاً بی فایده ارائه می دهد:
function preventEverything(e) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
}
document.addEventListener('click', preventEverything, true);
document.addEventListener('keydown', preventEverything, true);
document.addEventListener('mousedown', preventEverything, true);
document.addEventListener('contextmenu', preventEverything, true);
document.addEventListener('mousewheel', preventEverything, { capture: true, passive: false });
من واقعاً نمی دانم که چرا شما همیشه می خواهید این کار را انجام دهید (به جز شاید برای شوخی با کسی) ، اما فکر کردن در مورد آنچه در اینجا اتفاق می افتد مفید است و متوجه می شوید که چرا وضعیتی را ایجاد می کند.
همه وقایع از window
سرچشمه می گیرند ، بنابراین در این قطعه ، ما در حال متوقف شدن هستیم ، در آهنگ های آنها مرده ایم ، همه click
، keydown
، mousedown
، contextmenu
و mousewheel
رویدادها از رسیدن به هر عناصر که ممکن است برای آنها گوش کند ، اتفاق می افتد. ما همچنین از stopImmediatePropagation
تماس می گیریم تا هر یک از دستگیره ها پس از این یکی از آنها به سند بپردازند ، نیز خنثی می شوند.
توجه داشته باشید که stopPropagation()
و stopImmediatePropagation()
(حداقل بیشتر) نیستند که صفحه را بی فایده می کند. آنها به سادگی مانع از رسیدن وقایع به جایی می شوند که در غیر این صورت می روند.
اما ما همچنین با preventDefault()
تماس می گیریم ، که به یاد می آورید از اقدام پیش فرض جلوگیری می کند. بنابراین از هرگونه اقدامات پیش فرض (مانند پیمایش ماوس چرخ ، پیمایش صفحه کلید یا برجسته یا زبانه ، کلیک روی پیوند ، نمایش منوی زمینه و غیره) همه جلوگیری می شود ، بنابراین صفحه را در حالت نسبتاً بی فایده قرار می دهد.
دموهای زنده
برای کشف همه نمونه های این مقاله دوباره در یک مکان ، نسخه ی نمایشی تعبیه شده را در زیر مشاهده کنید.
قدردانی
تصویر قهرمان توسط تام ویلسون در Unsplash .
، preventDefault
و stopPropagation
: چه موقع از کدام روش استفاده می کنید و دقیقاً هر روش انجام می شود.
event.stoppropagation () و event.preventDefault ()
رسیدگی به رویداد JavaScript اغلب ساده است. این امر به ویژه در هنگام برخورد با یک ساختار HTML ساده (نسبتاً مسطح) صادق است. وقتی وقایع در حال مسافرت (یا انتشار) از طریق سلسله مراتب عناصر هستند ، چیزها کمی بیشتر درگیر می شوند. این به طور معمول زمانی است که توسعه دهندگان برای حل مشکلاتی که تجربه می کنند ، به stopPropagation()
و/یا preventDefault()
می رسند. اگر تا به حال به خودتان فکر کرده اید "من فقط preventDefault()
را امتحان می کنم و اگر این کار نکند ، من stopPropagation()
امتحان می کنم و اگر این کار نکند ، من هر دو را امتحان می کنم ،" پس این مقاله برای شما مناسب است! من دقیقاً توضیح خواهم داد که هر روش چه کاری انجام می دهد ، چه زمانی از کدام یک استفاده می شود و انواع مختلفی از کار را برای شما در اختیار شما قرار می دهد. هدف من پایان دادن به سردرگمی شما یک بار و برای همیشه است.
هرچند قبل از اینکه خیلی عمیق شویم ، مهم است که به طور خلاصه به دو نوع رسیدگی به رویداد ممکن در JavaScript لمس کنیم (در تمام مرورگرهای مدرن که - Enternet Explorer قبل از نسخه 9 به هیچ وجه از ضبط رویداد پشتیبانی نمی کند).
سبک های رویداد (ضبط و حباب)
همه مرورگرهای مدرن از ضبط رویداد پشتیبانی می کنند ، اما به ندرت توسط توسعه دهندگان مورد استفاده قرار می گیرد. جالب اینجاست که این تنها شکل رویداد بود که در ابتدا Netscape از آن پشتیبانی می کرد. بزرگترین رقیب Netscape ، Microsoft Internet Explorer ، به هیچ وجه از ضبط رویداد پشتیبانی نمی کرد ، بلکه فقط از سبک دیگری از رویداد به نام Bubbling Event پشتیبانی می کرد. هنگامی که W3C تشکیل شد ، آنها در هر دو سبک رویداد شایستگی پیدا کردند و اعلام کردند که مرورگرها باید از هر دو پارامتر به روش addEventListener
پشتیبانی کنند. در ابتدا ، این پارامتر فقط یک بولی بود ، اما همه مرورگرهای مدرن از یک شیء options
به عنوان پارامتر سوم پشتیبانی می کنند ، که اگر می خواهید از ضبط رویداد استفاده کنید یا نه ، می توانید از آن استفاده کنید (از جمله موارد دیگر):
someElement.addEventListener('click', myClickHandler, { capture: true | false });
توجه داشته باشید که شیء options
اختیاری است ، همانطور که خاصیت capture
آن است. اگر هر دو حذف شود ، مقدار پیش فرض برای capture
false
است ، به این معنی که از حباب رویداد استفاده می شود.
ضبط رویداد
چه معنایی دارد اگر کنترل کننده رویداد شما "در مرحله ضبط گوش کند؟" برای درک این موضوع ، باید بدانیم که چگونه وقایع سرچشمه می گیرند و چگونه آنها سفر می کنند. موارد زیر در مورد همه وقایع صادق است ، حتی اگر شما به عنوان توسعه دهنده از آن استفاده نکنید ، به آن اهمیت دهید یا به آن فکر کنید.
همه وقایع از پنجره شروع می شوند و ابتدا از مرحله ضبط عبور می کنند. این بدان معنی است که وقتی یک رویداد اعزام می شود ، پنجره را شروع می کند و ابتدا "به سمت پایین" به سمت عنصر هدف خود حرکت می کند. این اتفاق می افتد حتی اگر فقط در مرحله حباب گوش می دهید. نمونه زیر و جاوا اسکریپت را در نظر بگیرید:
<html>
<body>
<div id="A">
<div id="B">
<div id="C"></div>
</div>
</div>
</body>
</html>
document.getElementById('C').addEventListener(
'click',
function (e) {
console.log('#C was clicked');
},
true,
);
هنگامی که یک کاربر روی Element #C
کلیک می کند ، یک رویداد که از window
سرچشمه می گیرد ، ارسال می شود. این رویداد سپس از طریق فرزندان خود به شرح زیر پخش می شود:
window
=> document
=> <html>
=> <body>
=> و غیره ، تا رسیدن به هدف.
فرقی نمی کند که هیچ چیز برای یک رویداد کلیک در window
یا document
یا عنصر <html>
یا عنصر <body>
(یا هر عنصر دیگری در مسیر هدف آن) گوش نمی دهد. یک رویداد هنوز از window
سرچشمه می گیرد و سفر خود را همانطور که فقط توضیح داده شده است آغاز می کند.
در مثال ما ، رویداد کلیک سپس پخش می شود (این یک کلمه مهم است زیرا به طور مستقیم به نحوه کار روش stopPropagation()
کار می کند و بعداً در این سند توضیح داده می شود) از window
تا عنصر هدف خود (در این مورد ، #C
) از طریق هر عنصر بین window
و #C
.
این بدان معنی است که رویداد کلیک از window
شروع می شود و مرورگر سوالات زیر را می پرسد:
"آیا چیزی برای یک رویداد کلیک در window
در مرحله ضبط گوش می دهد؟" در این صورت ، دستگیرندگان رویداد مناسب آتش می گیرند. در مثال ما ، هیچ چیز نیست ، بنابراین هیچ یک از دستگیره ها آتش نمی گیرند.
در مرحله بعد ، این رویداد به document
پخش می شود و مرورگر می پرسد: "آیا چیزی برای یک رویداد کلیک روی document
در مرحله ضبط گوش می دهد؟" در این صورت ، دستگیرندگان رویداد مناسب آتش می گیرند.
در مرحله بعد ، این رویداد به عنصر <html>
پخش می شود و مرورگر می پرسد: "آیا هر چیزی برای کلیک بر روی عنصر <html>
در مرحله ضبط گوش می دهد؟" در این صورت ، دستگیرندگان رویداد مناسب آتش می گیرند.
در مرحله بعد ، این رویداد به عنصر <body>
پخش می شود و مرورگر می پرسد: "آیا چیزی برای یک رویداد کلیک در عنصر <body>
در مرحله ضبط گوش می دهد؟" در این صورت ، دستگیرندگان رویداد مناسب آتش می گیرند.
در مرحله بعد ، این رویداد به عنصر #A
پخش می شود. باز هم ، مرورگر سؤال خواهد کرد: "آیا هر چیزی که برای یک رویداد کلیک در #A
در مرحله ضبط گوش می دهد و اگر چنین است ، دستگیرندگان رویداد مناسب آتش می گیرند.
در مرحله بعد ، این رویداد به عنصر #B
پخش می شود (و همان سؤال پرسیده می شود).
سرانجام ، این رویداد به هدف خود خواهد رسید و مرورگر می پرسد: "آیا چیزی برای یک رویداد کلیک روی عنصر #C
در مرحله ضبط گوش می دهد؟" جواب این بار "بله!" این دوره کوتاه زمانی که این رویداد در هدف قرار دارد ، به عنوان "مرحله هدف" شناخته می شود. در این مرحله ، کنترل کننده رویداد آتش می زند ، مرورگر کنسول خواهد شد. log "#c کلیک شده است" و سپس ما تمام شدیم ، درست است؟ اشتباه! ما اصلاً تمام نشده ایم. این روند ادامه دارد ، اما اکنون در مرحله حباب تغییر می کند.
حباب رویداد
مرورگر سؤال خواهد کرد:
"آیا چیزی برای یک رویداد کلیک در #C
در مرحله حباب گوش می دهد؟" در اینجا توجه زیادی داشته باشید. گوش دادن به کلیک (یا هر نوع رویداد) در هر دو مرحله ضبط و حباب کاملاً امکان پذیر است. و اگر شما در هر دو مرحله دستگیرندگان رویداد را سیم کشی کرده اید (به عنوان مثال با فراخوانی .addEventListener()
دو بار ، یک بار با capture = true
و یک بار با capture = false
) ، پس بله ، هر دو دستگیرنده رویداد کاملاً برای همان عنصر آتش می گیرند. اما توجه به این نکته نیز حائز اهمیت است که آنها در مراحل مختلف آتش می گیرند (یکی در مرحله ضبط و دیگری در مرحله حباب).
در مرحله بعد ، این رویداد پخش می شود (بیشتر به عنوان "حباب" بیان شده است زیرا به نظر می رسد که این رویداد "Up" Tree Dom) را به عنصر والدین خود ، #B
، و مرورگر می پرسد: "آیا چیزی برای رویدادهای کلیک روی #B
در مرحله حباب گوش می دهد؟" در مثال ما ، هیچ چیز نیست ، بنابراین هیچ یک از دستگیره ها آتش نمی گیرند.
در مرحله بعد ، این رویداد به #A
حباب می شود و مرورگر می پرسد: "آیا چیزی برای رویدادهای کلیک در #A
در مرحله حباب گوش می دهد؟"
در مرحله بعد ، این رویداد به <body>
حباب می شود: "آیا چیزی برای رویدادهای کلیک روی عنصر <body>
در مرحله حباب گوش می دهد؟"
در مرحله بعد ، عنصر <html>
: "آیا چیزی برای رویدادهای کلیک در عنصر <html>
در مرحله حباب گوش می کند؟
در مرحله بعد ، document
: "آیا هر چیزی برای رویدادهای کلیک روی document
در مرحله حباب گوش می دهد؟"
سرانجام ، window
: "آیا هر چیزی که برای رویدادهای کلیک روی پنجره در مرحله حباب گوش می دهد؟"
اوه! این یک سفر طولانی بود ، و رویداد ما احتمالاً تاکنون بسیار خسته است ، اما باور کنید یا نه ، این همان سفر است که هر رویدادی از آن عبور می کند! بیشتر اوقات ، این هرگز مورد توجه قرار نمی گیرد زیرا توسعه دهندگان معمولاً فقط به یک مرحله رویداد یا مرحله دیگر علاقه مند هستند (و این معمولاً مرحله حباب است).
ارزش این را دارد که مدتی را با ضبط رویداد و حباب رویداد و ورود برخی از یادداشت ها به کنسول به عنوان آتش سوزی ، صرف کنید. دیدن مسیری که یک رویداد طی می کند بسیار روشنگری است. در اینجا مثالی وجود دارد که در هر دو مرحله به هر عنصر گوش می دهد.
<html>
<body>
<div id="A">
<div id="B">
<div id="C"></div>
</div>
</div>
</body>
</html>
document.addEventListener(
'click',
function (e) {
console.log('click on document in capturing phase');
},
true,
);
// document.documentElement == <html>
document.documentElement.addEventListener(
'click',
function (e) {
console.log('click on <html> in capturing phase');
},
true,
);
document.body.addEventListener(
'click',
function (e) {
console.log('click on <body> in capturing phase');
},
true,
);
document.getElementById('A').addEventListener(
'click',
function (e) {
console.log('click on #A in capturing phase');
},
true,
);
document.getElementById('B').addEventListener(
'click',
function (e) {
console.log('click on #B in capturing phase');
},
true,
);
document.getElementById('C').addEventListener(
'click',
function (e) {
console.log('click on #C in capturing phase');
},
true,
);
document.addEventListener(
'click',
function (e) {
console.log('click on document in bubbling phase');
},
false,
);
// document.documentElement == <html>
document.documentElement.addEventListener(
'click',
function (e) {
console.log('click on <html> in bubbling phase');
},
false,
);
document.body.addEventListener(
'click',
function (e) {
console.log('click on <body> in bubbling phase');
},
false,
);
document.getElementById('A').addEventListener(
'click',
function (e) {
console.log('click on #A in bubbling phase');
},
false,
);
document.getElementById('B').addEventListener(
'click',
function (e) {
console.log('click on #B in bubbling phase');
},
false,
);
document.getElementById('C').addEventListener(
'click',
function (e) {
console.log('click on #C in bubbling phase');
},
false,
);
خروجی کنسول به کدام عنصر کلیک می کند. اگر قرار بود روی "عمیق ترین" عنصر در درخت DOM (عنصر #C
) کلیک کنید ، هر یک از این رویدادها را آتش می بینید. با کمی یک ظاهر طراحی شده CSS برای آشکار تر کردن کدام عنصر ، در اینجا عنصر خروجی کنسول #C
(با یک تصویر نیز) وجود دارد:
"click on document in capturing phase"
"click on <html> in capturing phase"
"click on <body> in capturing phase"
"click on #A in capturing phase"
"click on #B in capturing phase"
"click on #C in capturing phase"
"click on #C in bubbling phase"
"click on #B in bubbling phase"
"click on #A in bubbling phase"
"click on <body> in bubbling phase"
"click on <html> in bubbling phase"
"click on document in bubbling phase"
شما می توانید به طور تعاملی با این کار در نسخه ی نمایشی زنده زیر بازی کنید. روی عنصر #C
کلیک کرده و خروجی کنسول را مشاهده کنید.
event.stopPropagation()
با درک این مسئله که وقایع از کجا سرچشمه می گیرند و چگونه آنها (یعنی پخش می شوند) از طریق DOM در هر دو مرحله ضبط و مرحله حباب ، اکنون می توانیم توجه خود را به event.stopPropagation()
بکشیم.
روش stopPropagation()
را می توان در مورد (بیشترین) وقایع DOM بومی فراخوانی کرد. من می گویم "بیشتر" زیرا تعداد معدودی وجود دارد که با این روش این روش هیچ کاری انجام نمی دهند (زیرا این رویداد برای شروع پخش نمی شود). رویدادهایی مانند focus
، blur
، load
، scroll
و چند نفر دیگر در این گروه قرار می گیرند. شما می توانید stopPropagation()
را صدا کنید اما هیچ چیز جالبی اتفاق نمی افتد ، زیرا این رویدادها تبلیغ نمی شوند.
اما stopPropagation
چه کاری انجام می دهد؟
این تقریباً همان چیزی است که می گوید. هنگامی که شما آن را صدا می کنید ، این رویداد از آن نقطه ، انتشار به هر عناصری را که در غیر این صورت به آن سفر می کند ، متوقف می کند. این در مورد هر دو جهت (ضبط و حباب) صادق است. بنابراین اگر در هر نقطه از مرحله ضبط ، stopPropagation()
را صدا می کنید ، این رویداد هرگز آن را به مرحله هدف یا مرحله حباب نمی رساند. اگر آن را در مرحله حباب صدا کنید ، در حال حاضر مرحله ضبط را پشت سر می گذارد ، اما "حباب کردن" را از نقطه ای که شما آن را صدا می کنید متوقف می شود.
با بازگشت به نشانه مثال ما ، فکر می کنید چه اتفاقی می افتد ، اگر ما در مرحله ضبط در عنصر #B
نامیدیم که stopPropagation()
خوانده ایم؟
این منجر به خروجی زیر می شود:
"click on document in capturing phase"
"click on <html> in capturing phase"
"click on <body> in capturing phase"
"click on #A in capturing phase"
"click on #B in capturing phase"
شما می توانید به طور تعاملی با این کار در نسخه ی نمایشی زنده زیر بازی کنید. روی عنصر #C
در نسخه ی نمایشی زنده کلیک کرده و خروجی کنسول را مشاهده کنید.
در مورد متوقف کردن انتشار در #A
در مرحله حباب چطور؟ این منجر به خروجی زیر می شود:
"click on document in capturing phase"
"click on <html> in capturing phase"
"click on <body> in capturing phase"
"click on #A in capturing phase"
"click on #B in capturing phase"
"click on #C in capturing phase"
"click on #C in bubbling phase"
"click on #B in bubbling phase"
"click on #A in bubbling phase"
شما می توانید به طور تعاملی با این کار در نسخه ی نمایشی زنده زیر بازی کنید. روی عنصر #C
در نسخه ی نمایشی زنده کلیک کرده و خروجی کنسول را مشاهده کنید.
یکی دیگه فقط برای سرگرمی چه اتفاقی می افتد اگر ما در مرحله هدف برای #C
stopPropagation()
تماس بگیریم؟ به یاد بیاورید که "مرحله هدف" نامی است که به دوره زمانی که این رویداد در هدف آن است ، داده می شود. این منجر به خروجی زیر می شود:
"click on document in capturing phase"
"click on <html> in capturing phase"
"click on <body> in capturing phase"
"click on #A in capturing phase"
"click on #B in capturing phase"
"click on #C in capturing phase"
توجه داشته باشید که کنترل کننده رویداد برای #C
که در آن وارد می شویم "روی #c در مرحله ضبط کلیک کنید" هنوز هم اجرا می شود ، اما موردی که در آن "روی #c در مرحله حباب کلیک می کنیم" وارد می شویم. این باید کاملاً منطقی باشد. ما stopPropagation()
از سابق خواندیم ، بنابراین این نقطه ای است که در آن انتشار این رویداد متوقف می شود.
شما می توانید به طور تعاملی با این کار در نسخه ی نمایشی زنده زیر بازی کنید. روی عنصر #C
در نسخه ی نمایشی زنده کلیک کرده و خروجی کنسول را مشاهده کنید.
در هر یک از این نسخه های نمایشی زنده ، من شما را تشویق می کنم که در اطراف بازی کنید. فقط روی عنصر #A
فقط یا فقط عنصر body
کلیک کنید. سعی کنید پیش بینی کنید که چه اتفاقی خواهد افتاد و اگر صحیح هستید ، مشاهده کنید. در این مرحله ، شما باید بتوانید بسیار دقیق پیش بینی کنید.
event.stopImmediatePropagation()
این روش عجیب و غریب و غیر مورد استفاده چیست؟ این شبیه به stopPropagation
است ، اما به جای متوقف کردن یک رویداد از سفر به فرزندان (گرفتن) یا اجداد (حباب) ، این روش فقط زمانی اعمال می شود که بیش از یک کنترل کننده رویداد را به یک عنصر واحد تبدیل کنید. از آنجا که addEventListener()
از یک سبک چند مرحله ای از رویداد پشتیبانی می کند ، می توان بیش از یک بار یک کنترل کننده رویداد را به یک عنصر واحد تبدیل کرد. وقتی این اتفاق بیفتد ، (در بیشتر مرورگرها) ، دستگیرندگان رویداد به ترتیب سیم کشی اعدام می شوند. فراخوانی stopImmediatePropagation()
مانع از شلیک هر یک از دستگیران بعدی می شود. به مثال زیر توجه کنید:
<html>
<body>
<div id="A">I am the #A element</div>
</body>
</html>
document.getElementById('A').addEventListener(
'click',
function (e) {
console.log('When #A is clicked, I shall run first!');
},
false,
);
document.getElementById('A').addEventListener(
'click',
function (e) {
console.log('When #A is clicked, I shall run second!');
e.stopImmediatePropagation();
},
false,
);
document.getElementById('A').addEventListener(
'click',
function (e) {
console.log('When #A is clicked, I would have run third, if not for stopImmediatePropagation');
},
false,
);
مثال فوق منجر به خروجی کنسول زیر خواهد شد:
"When #A is clicked, I shall run first!"
"When #A is clicked, I shall run second!"
توجه داشته باشید که دستیار سومین رویداد هرگز به دلیل این واقعیت که کنترل کننده رویداد دوم e.stopImmediatePropagation()
را صدا نمی کند ، هرگز اجرا نمی شود. اگر در عوض به نام e.stopPropagation()
خوانده می شدیم ، کنترل کننده سوم هنوز اجرا می شود.
event.preventDefault()
اگر stopPropagation()
مانع از مسافرت یک رویداد "به سمت پایین" (ضبط) یا "به سمت بالا" (حباب) شود ، پس از آن چه preventDefault()
انجام می دهد؟ به نظر می رسد که کاری مشابه انجام می دهد. آیا آن را انجام می دهد؟
نه واقعا. در حالی که این دو اغلب گیج هستند ، در واقع آنها ارتباط چندانی با یکدیگر ندارند. هنگامی که می بینید preventDefault()
، در ذهن خود ، کلمه "عمل" را اضافه کنید. فکر کنید "از اقدام پیش فرض جلوگیری کنید."
و اقدام پیش فرض ممکن است از شما چیست؟ متأسفانه ، پاسخ به آن کاملاً واضح نیست زیرا به ترکیب Element + رویداد مورد نظر بسیار وابسته است. و برای اینکه مسائل حتی گیج کننده تر شود ، گاهی اوقات هیچ اقدامی پیش فرض وجود ندارد!
بیایید با یک مثال بسیار ساده برای درک شروع کنیم. انتظار دارید وقتی روی یک پیوند در یک صفحه وب کلیک می کنید چه اتفاقی بیفتد؟ بدیهی است ، شما انتظار دارید که مرورگر به URL مشخص شده توسط آن لینک حرکت کند. در این حالت ، عنصر یک برچسب لنگر است و رویداد یک رویداد کلیک است. این ترکیب ( <a>
+ click
) دارای "اقدام پیش فرض" برای حرکت به HREF پیوند است. اگر می خواستید مرورگر را از انجام آن عمل پیش فرض جلوگیری کنید ، چه می کنید؟ یعنی فرض کنید می خواهید از مرورگر جلوگیری کنید به URL مشخص شده توسط ویژگی href
عنصر <a>
؟ این همان کاری است که preventDefault()
برای شما انجام خواهد داد. این مثال را در نظر بگیرید:
<a id="avett" href="https://www.theavettbrothers.com/welcome">The Avett Brothers</a>
document.getElementById('avett').addEventListener(
'click',
function (e) {
e.preventDefault();
console.log('Maybe we should just play some of their music right here instead?');
},
false,
);
شما می توانید به طور تعاملی با این کار در نسخه ی نمایشی زنده زیر بازی کنید. روی پیوند برادران Avett کلیک کرده و خروجی کنسول را مشاهده کنید (و این واقعیت که شما به وب سایت برادران Avett هدایت نمی شوید).
به طور معمول ، با کلیک بر روی لینک با برچسب برادران Avett منجر به مرور به www.theavettbrothers.com
می شود. در این حالت ، ما یک کنترل کننده رویداد را به عنصر <a>
سیم کشی کرده ایم و مشخص کردیم که باید از اقدامات پیش فرض جلوگیری شود. بنابراین ، هنگامی که کاربر روی این لینک کلیک می کند ، آنها در هیچ کجا پیمایش نمی شوند ، و در عوض کنسول به سادگی وارد می شود "شاید ما فقط باید در اینجا برخی از موسیقی آنها را پخش کنیم؟"
چه ترکیب های عنصر/رویداد دیگری به شما امکان می دهد از اقدام پیش فرض جلوگیری کنید؟ من نمی توانم همه آنها را لیست کنم ، و گاهی اوقات شما فقط باید آزمایش کنید. اما به طور خلاصه ، در اینجا چند مورد وجود دارد:
<form>
Element + "ارسال" رویداد:preventDefault()
برای این ترکیب مانع از ارسال فرم می شود. این مفید است اگر می خواهید اعتبار سنجی را انجام دهید و در صورت عدم موفقیت چیزی ، می توانید به طور مشروط با PresideDefault تماس بگیرید تا جلوی ارسال فرم را بگیرد.<a>
عنصر + رویداد "کلیک":preventDefault()
برای این ترکیب مانع از حرکت مرورگر به URL مشخص شده در ویژگی HREF عنصر<a>
می شود.document
+ رویداد "Mousewheel" رویداد:preventDefault()
برای این ترکیب مانع از پیمایش صفحه با ماوس می شود (پیمایش با صفحه کلید هنوز هم کار می کند).
↜ این نیاز به فراخوانیaddEventListener()
با{ passive: false }
.document
+ رویداد "keydown" رویداد:preventDefault()
برای این ترکیب کشنده است. این صفحه را تا حد زیادی بی فایده می کند و از پیمایش صفحه کلید ، زبانه و برجسته صفحه کلید جلوگیری می کند.document
+ رویداد "Mousedown":preventDefault()
برای این ترکیب مانع از برجسته کردن متن با ماوس و هر اقدام "پیش فرض" دیگری می شود که شخص با یک ماوس به پایین می رود.<input>
Element + "KeyPress" رویداد:preventDefault()
برای این ترکیب باعث می شود شخصیت های تایپ شده توسط کاربر به عنصر ورودی تایپ شوند (اما این کار را انجام ندهید ؛ به ندرت ، اگر همیشه دلیل معتبری برای آن وجود دارد).document
+ رویداد "ContextMenu":preventDefault()
برای این ترکیب مانع از ظاهر شدن منوی زمینه مرورگر بومی می شود وقتی کاربر راست کلیک می کند یا فشار طولانی (یا هر راه دیگری که ممکن است یک منوی زمینه ظاهر شود).
این یک لیست جامع به هیچ وجه نیست ، اما امیدوارم که ایده خوبی در مورد چگونگی استفاده preventDefault()
باشد.
یک شوخی عملی سرگرم کننده؟
چه اتفاقی می افتد اگر شما در مرحله ضبط ، شروع به کار در مرحله ضبط ، و پیشبرد () و preventDefault()
stopPropagation()
؟ شوخ طبعی به وجود می آید! قطعه کد زیر هر صفحه وب را کاملاً بی فایده ارائه می دهد:
function preventEverything(e) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
}
document.addEventListener('click', preventEverything, true);
document.addEventListener('keydown', preventEverything, true);
document.addEventListener('mousedown', preventEverything, true);
document.addEventListener('contextmenu', preventEverything, true);
document.addEventListener('mousewheel', preventEverything, { capture: true, passive: false });
من واقعاً نمی دانم که چرا شما همیشه می خواهید این کار را انجام دهید (به جز شاید برای شوخی با کسی) ، اما فکر کردن در مورد آنچه در اینجا اتفاق می افتد مفید است و متوجه می شوید که چرا وضعیتی را ایجاد می کند.
همه وقایع از window
سرچشمه می گیرند ، بنابراین در این قطعه ، ما در حال متوقف شدن هستیم ، در آهنگ های آنها مرده ایم ، همه click
، keydown
، mousedown
، contextmenu
و mousewheel
رویدادها از رسیدن به هر عناصر که ممکن است برای آنها گوش کند ، اتفاق می افتد. ما همچنین از stopImmediatePropagation
تماس می گیریم تا هر یک از دستگیره ها پس از این یکی از آنها به سند بپردازند ، نیز خنثی می شوند.
توجه داشته باشید که stopPropagation()
و stopImmediatePropagation()
(حداقل بیشتر) نیستند که صفحه را بی فایده می کند. آنها به سادگی مانع از رسیدن وقایع به جایی می شوند که در غیر این صورت می روند.
اما ما همچنین با preventDefault()
تماس می گیریم ، که به یاد می آورید از اقدام پیش فرض جلوگیری می کند. بنابراین از هرگونه اقدامات پیش فرض (مانند پیمایش ماوس چرخ ، پیمایش صفحه کلید یا برجسته یا زبانه ، کلیک روی پیوند ، نمایش منوی زمینه و غیره) همه جلوگیری می شود ، بنابراین صفحه را در حالت نسبتاً بی فایده قرار می دهد.
دموهای زنده
برای کشف همه نمونه های این مقاله دوباره در یک مکان ، نسخه ی نمایشی تعبیه شده را در زیر مشاهده کنید.
قدردانی
تصویر قهرمان توسط تام ویلسون در Unsplash .
، preventDefault
و stopPropagation
: چه موقع از کدام روش استفاده می کنید و دقیقاً هر روش انجام می شود.
event.stoppropagation () و event.preventDefault ()
رسیدگی به رویداد JavaScript اغلب ساده است. این امر به ویژه در هنگام برخورد با یک ساختار HTML ساده (نسبتاً مسطح) صادق است. وقتی وقایع در حال مسافرت (یا انتشار) از طریق سلسله مراتب عناصر هستند ، چیزها کمی بیشتر درگیر می شوند. این به طور معمول زمانی است که توسعه دهندگان برای حل مشکلاتی که تجربه می کنند ، به stopPropagation()
و/یا preventDefault()
می رسند. اگر تا به حال به خودتان فکر کرده اید "من فقط preventDefault()
را امتحان می کنم و اگر این کار نکند ، من stopPropagation()
امتحان می کنم و اگر این کار نکند ، من هر دو را امتحان می کنم ،" پس این مقاله برای شما مناسب است! من دقیقاً توضیح خواهم داد که هر روش چه کاری انجام می دهد ، چه زمانی از کدام یک استفاده می شود و انواع مختلفی از کار را برای شما در اختیار شما قرار می دهد. هدف من پایان دادن به سردرگمی شما یک بار و برای همیشه است.
هرچند قبل از اینکه خیلی عمیق شویم ، مهم است که به طور خلاصه به دو نوع رسیدگی به رویداد ممکن در JavaScript لمس کنیم (در تمام مرورگرهای مدرن که - Enternet Explorer قبل از نسخه 9 به هیچ وجه از ضبط رویداد پشتیبانی نمی کند).
سبک های رویداد (ضبط و حباب)
همه مرورگرهای مدرن از ضبط رویداد پشتیبانی می کنند ، اما به ندرت توسط توسعه دهندگان مورد استفاده قرار می گیرد. جالب اینجاست که این تنها شکل رویداد بود که در ابتدا Netscape از آن پشتیبانی می کرد. بزرگترین رقیب Netscape ، Microsoft Internet Explorer ، به هیچ وجه از ضبط رویداد پشتیبانی نمی کرد ، بلکه فقط از سبک دیگری از رویداد به نام Bubbling Event پشتیبانی می کرد. هنگامی که W3C تشکیل شد ، آنها در هر دو سبک رویداد شایستگی پیدا کردند و اعلام کردند که مرورگرها باید از هر دو پارامتر به روش addEventListener
پشتیبانی کنند. در ابتدا ، این پارامتر فقط یک بولی بود ، اما همه مرورگرهای مدرن از یک شیء options
به عنوان پارامتر سوم پشتیبانی می کنند ، که اگر می خواهید از ضبط رویداد استفاده کنید یا نه ، می توانید از آن استفاده کنید (از جمله موارد دیگر):
someElement.addEventListener('click', myClickHandler, { capture: true | false });
توجه داشته باشید که شیء options
اختیاری است ، همانطور که خاصیت capture
آن است. اگر هر دو حذف شود ، مقدار پیش فرض برای capture
false
است ، به این معنی که از حباب رویداد استفاده می شود.
ضبط رویداد
چه معنایی دارد اگر کنترل کننده رویداد شما "در مرحله ضبط گوش کند؟" برای درک این موضوع ، باید بدانیم که چگونه وقایع سرچشمه می گیرند و چگونه آنها سفر می کنند. موارد زیر در مورد همه وقایع صادق است ، حتی اگر شما به عنوان توسعه دهنده از آن استفاده نکنید ، به آن اهمیت دهید یا به آن فکر کنید.
همه وقایع از پنجره شروع می شوند و ابتدا از مرحله ضبط عبور می کنند. این بدان معنی است که وقتی یک رویداد اعزام می شود ، پنجره را شروع می کند و ابتدا "به سمت پایین" به سمت عنصر هدف خود حرکت می کند. این اتفاق می افتد حتی اگر فقط در مرحله حباب گوش می دهید. نمونه زیر و جاوا اسکریپت را در نظر بگیرید:
<html>
<body>
<div id="A">
<div id="B">
<div id="C"></div>
</div>
</div>
</body>
</html>
document.getElementById('C').addEventListener(
'click',
function (e) {
console.log('#C was clicked');
},
true,
);
هنگامی که یک کاربر روی Element #C
کلیک می کند ، یک رویداد که از window
سرچشمه می گیرد ، ارسال می شود. این رویداد سپس از طریق فرزندان خود به شرح زیر پخش می شود:
window
=> document
=> <html>
=> <body>
=> و غیره ، تا رسیدن به هدف.
فرقی نمی کند که هیچ چیز برای یک رویداد کلیک در window
یا document
یا عنصر <html>
یا عنصر <body>
(یا هر عنصر دیگری در مسیر هدف آن) گوش نمی دهد. یک رویداد هنوز از window
سرچشمه می گیرد و سفر خود را همانطور که فقط توضیح داده شده است آغاز می کند.
در مثال ما ، رویداد کلیک سپس پخش می شود (این یک کلمه مهم است زیرا به طور مستقیم به نحوه کار روش stopPropagation()
کار می کند و بعداً در این سند توضیح داده می شود) از window
تا عنصر هدف خود (در این مورد ، #C
) از طریق هر عنصر بین window
و #C
.
این بدان معنی است که رویداد کلیک از window
شروع می شود و مرورگر سوالات زیر را می پرسد:
"آیا چیزی برای یک رویداد کلیک در window
در مرحله ضبط گوش می دهد؟" در این صورت ، دستگیرندگان رویداد مناسب آتش می گیرند. در مثال ما ، هیچ چیز نیست ، بنابراین هیچ یک از دستگیره ها آتش نمی گیرند.
در مرحله بعد ، این رویداد به document
پخش می شود و مرورگر می پرسد: "آیا چیزی برای یک رویداد کلیک روی document
در مرحله ضبط گوش می دهد؟" در این صورت ، دستگیرندگان رویداد مناسب آتش می گیرند.
در مرحله بعد ، این رویداد به عنصر <html>
پخش می شود و مرورگر می پرسد: "آیا هر چیزی برای کلیک بر روی عنصر <html>
در مرحله ضبط گوش می دهد؟" در این صورت ، دستگیرندگان رویداد مناسب آتش می گیرند.
در مرحله بعد ، این رویداد به عنصر <body>
پخش می شود و مرورگر می پرسد: "آیا چیزی برای یک رویداد کلیک در عنصر <body>
در مرحله ضبط گوش می دهد؟" در این صورت ، دستگیرندگان رویداد مناسب آتش می گیرند.
در مرحله بعد ، این رویداد به عنصر #A
پخش می شود. باز هم ، مرورگر سؤال خواهد کرد: "آیا هر چیزی که برای یک رویداد کلیک در #A
در مرحله ضبط گوش می دهد و اگر چنین است ، دستگیرندگان رویداد مناسب آتش می گیرند.
در مرحله بعد ، این رویداد به عنصر #B
پخش می شود (و همان سؤال پرسیده می شود).
سرانجام ، این رویداد به هدف خود خواهد رسید و مرورگر می پرسد: "آیا چیزی برای یک رویداد کلیک روی عنصر #C
در مرحله ضبط گوش می دهد؟" جواب این بار "بله!" این دوره کوتاه زمانی که این رویداد در هدف قرار دارد ، به عنوان "مرحله هدف" شناخته می شود. در این مرحله ، کنترل کننده رویداد آتش می زند ، مرورگر کنسول خواهد شد. log "#c کلیک شده است" و سپس ما تمام شدیم ، درست است؟ اشتباه! ما اصلاً تمام نشده ایم. این روند ادامه دارد ، اما اکنون در مرحله حباب تغییر می کند.
حباب رویداد
مرورگر سؤال خواهد کرد:
"آیا چیزی برای یک رویداد کلیک در #C
در مرحله حباب گوش می دهد؟" در اینجا توجه زیادی داشته باشید. گوش دادن به کلیک (یا هر نوع رویداد) در هر دو مرحله ضبط و حباب کاملاً امکان پذیر است. و اگر شما در هر دو مرحله دستگیرندگان رویداد را سیم کشی کرده اید (به عنوان مثال با فراخوانی .addEventListener()
دو بار ، یک بار با capture = true
و یک بار با capture = false
) ، پس بله ، هر دو دستگیرنده رویداد کاملاً برای همان عنصر آتش می گیرند. اما توجه به این نکته نیز حائز اهمیت است که آنها در مراحل مختلف آتش می گیرند (یکی در مرحله ضبط و دیگری در مرحله حباب).
در مرحله بعد ، این رویداد پخش می شود (بیشتر به عنوان "حباب" بیان شده است زیرا به نظر می رسد که این رویداد "Up" Tree Dom) را به عنصر والدین خود ، #B
، و مرورگر می پرسد: "آیا چیزی برای رویدادهای کلیک روی #B
در مرحله حباب گوش می دهد؟" در مثال ما ، هیچ چیز نیست ، بنابراین هیچ یک از دستگیره ها آتش نمی گیرند.
در مرحله بعد ، این رویداد به #A
حباب می شود و مرورگر می پرسد: "آیا چیزی برای رویدادهای کلیک در #A
در مرحله حباب گوش می دهد؟"
در مرحله بعد ، این رویداد به <body>
حباب می شود: "آیا چیزی برای رویدادهای کلیک روی عنصر <body>
در مرحله حباب گوش می دهد؟"
در مرحله بعد ، عنصر <html>
: "آیا چیزی برای رویدادهای کلیک در عنصر <html>
در مرحله حباب گوش می کند؟
در مرحله بعد ، document
: "آیا هر چیزی برای رویدادهای کلیک روی document
در مرحله حباب گوش می دهد؟"
سرانجام ، window
: "آیا هر چیزی که برای رویدادهای کلیک روی پنجره در مرحله حباب گوش می دهد؟"
اوه! این یک سفر طولانی بود ، و رویداد ما احتمالاً تاکنون بسیار خسته است ، اما باور کنید یا نه ، این همان سفر است که هر رویدادی از آن عبور می کند! بیشتر اوقات ، این هرگز مورد توجه قرار نمی گیرد زیرا توسعه دهندگان معمولاً فقط به یک مرحله رویداد یا مرحله دیگر علاقه مند هستند (و این معمولاً مرحله حباب است).
ارزش این را دارد که مدتی را با ضبط رویداد و حباب رویداد و ورود برخی از یادداشت ها به کنسول به عنوان آتش سوزی ، صرف کنید. دیدن مسیری که یک رویداد طی می کند بسیار روشنگری است. در اینجا مثالی وجود دارد که در هر دو مرحله به هر عنصر گوش می دهد.
<html>
<body>
<div id="A">
<div id="B">
<div id="C"></div>
</div>
</div>
</body>
</html>
document.addEventListener(
'click',
function (e) {
console.log('click on document in capturing phase');
},
true,
);
// document.documentElement == <html>
document.documentElement.addEventListener(
'click',
function (e) {
console.log('click on <html> in capturing phase');
},
true,
);
document.body.addEventListener(
'click',
function (e) {
console.log('click on <body> in capturing phase');
},
true,
);
document.getElementById('A').addEventListener(
'click',
function (e) {
console.log('click on #A in capturing phase');
},
true,
);
document.getElementById('B').addEventListener(
'click',
function (e) {
console.log('click on #B in capturing phase');
},
true,
);
document.getElementById('C').addEventListener(
'click',
function (e) {
console.log('click on #C in capturing phase');
},
true,
);
document.addEventListener(
'click',
function (e) {
console.log('click on document in bubbling phase');
},
false,
);
// document.documentElement == <html>
document.documentElement.addEventListener(
'click',
function (e) {
console.log('click on <html> in bubbling phase');
},
false,
);
document.body.addEventListener(
'click',
function (e) {
console.log('click on <body> in bubbling phase');
},
false,
);
document.getElementById('A').addEventListener(
'click',
function (e) {
console.log('click on #A in bubbling phase');
},
false,
);
document.getElementById('B').addEventListener(
'click',
function (e) {
console.log('click on #B in bubbling phase');
},
false,
);
document.getElementById('C').addEventListener(
'click',
function (e) {
console.log('click on #C in bubbling phase');
},
false,
);
خروجی کنسول به کدام عنصر کلیک می کند. اگر قرار بود روی "عمیق ترین" عنصر در درخت DOM (عنصر #C
) کلیک کنید ، هر یک از این رویدادها را آتش می بینید. با کمی یک ظاهر طراحی شده CSS برای آشکار تر کردن کدام عنصر ، در اینجا عنصر خروجی کنسول #C
(با یک تصویر نیز) وجود دارد:
"click on document in capturing phase"
"click on <html> in capturing phase"
"click on <body> in capturing phase"
"click on #A in capturing phase"
"click on #B in capturing phase"
"click on #C in capturing phase"
"click on #C in bubbling phase"
"click on #B in bubbling phase"
"click on #A in bubbling phase"
"click on <body> in bubbling phase"
"click on <html> in bubbling phase"
"click on document in bubbling phase"
شما می توانید به طور تعاملی با این کار در نسخه ی نمایشی زنده زیر بازی کنید. روی عنصر #C
کلیک کرده و خروجی کنسول را مشاهده کنید.
event.stopPropagation()
با درک این مسئله که وقایع از کجا سرچشمه می گیرند و چگونه آنها (یعنی پخش می شوند) از طریق DOM در هر دو مرحله ضبط و مرحله حباب ، اکنون می توانیم توجه خود را به event.stopPropagation()
بکشیم.
روش stopPropagation()
را می توان در مورد (بیشترین) وقایع DOM بومی فراخوانی کرد. من می گویم "بیشتر" زیرا تعداد معدودی وجود دارد که با این روش این روش هیچ کاری انجام نمی دهند (زیرا این رویداد برای شروع پخش نمی شود). رویدادهایی مانند focus
، blur
، load
، scroll
و چند نفر دیگر در این گروه قرار می گیرند. شما می توانید stopPropagation()
را صدا کنید اما هیچ چیز جالبی اتفاق نمی افتد ، زیرا این رویدادها تبلیغ نمی شوند.
اما stopPropagation
چه کاری انجام می دهد؟
این تقریباً همان چیزی است که می گوید. هنگامی که شما آن را صدا می کنید ، این رویداد از آن نقطه ، انتشار به هر عناصری را که در غیر این صورت به آن سفر می کند ، متوقف می کند. این در مورد هر دو جهت (ضبط و حباب) صادق است. بنابراین اگر در هر نقطه از مرحله ضبط ، stopPropagation()
را صدا می کنید ، این رویداد هرگز آن را به مرحله هدف یا مرحله حباب نمی رساند. اگر آن را در مرحله حباب صدا کنید ، در حال حاضر مرحله ضبط را پشت سر می گذارد ، اما "حباب کردن" را از نقطه ای که شما آن را صدا می کنید متوقف می شود.
با بازگشت به نشانه مثال ما ، فکر می کنید چه اتفاقی می افتد ، اگر ما در مرحله ضبط در عنصر #B
نامیدیم که stopPropagation()
خوانده ایم؟
این منجر به خروجی زیر می شود:
"click on document in capturing phase"
"click on <html> in capturing phase"
"click on <body> in capturing phase"
"click on #A in capturing phase"
"click on #B in capturing phase"
شما می توانید به طور تعاملی با این کار در نسخه ی نمایشی زنده زیر بازی کنید. روی عنصر #C
در نسخه ی نمایشی زنده کلیک کرده و خروجی کنسول را مشاهده کنید.
در مورد متوقف کردن انتشار در #A
در مرحله حباب چطور؟ این منجر به خروجی زیر می شود:
"click on document in capturing phase"
"click on <html> in capturing phase"
"click on <body> in capturing phase"
"click on #A in capturing phase"
"click on #B in capturing phase"
"click on #C in capturing phase"
"click on #C in bubbling phase"
"click on #B in bubbling phase"
"click on #A in bubbling phase"
شما می توانید به طور تعاملی با این کار در نسخه ی نمایشی زنده زیر بازی کنید. روی عنصر #C
در نسخه ی نمایشی زنده کلیک کرده و خروجی کنسول را مشاهده کنید.
یکی دیگه فقط برای سرگرمی چه اتفاقی می افتد اگر ما در مرحله هدف برای #C
stopPropagation()
تماس بگیریم؟ به یاد بیاورید که "مرحله هدف" نامی است که به دوره زمانی که این رویداد در هدف آن است ، داده می شود. این منجر به خروجی زیر می شود:
"click on document in capturing phase"
"click on <html> in capturing phase"
"click on <body> in capturing phase"
"click on #A in capturing phase"
"click on #B in capturing phase"
"click on #C in capturing phase"
توجه داشته باشید که کنترل کننده رویداد برای #C
که در آن وارد می شویم "روی #c در مرحله ضبط کلیک کنید" هنوز هم اجرا می شود ، اما موردی که در آن "روی #c در مرحله حباب کلیک می کنیم" وارد می شویم. این باید کاملاً منطقی باشد. ما stopPropagation()
از سابق خواندیم ، بنابراین این نقطه ای است که در آن انتشار این رویداد متوقف می شود.
شما می توانید به طور تعاملی با این کار در نسخه ی نمایشی زنده زیر بازی کنید. روی عنصر #C
در نسخه ی نمایشی زنده کلیک کرده و خروجی کنسول را مشاهده کنید.
در هر یک از این نسخه های نمایشی زنده ، من شما را تشویق می کنم که در اطراف بازی کنید. فقط روی عنصر #A
فقط یا فقط عنصر body
کلیک کنید. سعی کنید پیش بینی کنید که چه اتفاقی خواهد افتاد و اگر صحیح هستید ، مشاهده کنید. در این مرحله ، شما باید بتوانید بسیار دقیق پیش بینی کنید.
event.stopImmediatePropagation()
این روش عجیب و غریب و غیر مورد استفاده چیست؟ این شبیه به stopPropagation
است ، اما به جای متوقف کردن یک رویداد از سفر به فرزندان (گرفتن) یا اجداد (حباب) ، این روش فقط زمانی اعمال می شود که بیش از یک کنترل کننده رویداد را به یک عنصر واحد تبدیل کنید. از آنجا که addEventListener()
از یک سبک چند مرحله ای از رویداد پشتیبانی می کند ، می توان بیش از یک بار یک کنترل کننده رویداد را به یک عنصر واحد تبدیل کرد. وقتی این اتفاق بیفتد ، (در بیشتر مرورگرها) ، دستگیرندگان رویداد به ترتیب سیم کشی اعدام می شوند. فراخوانی stopImmediatePropagation()
مانع از شلیک هر یک از دستگیران بعدی می شود. به مثال زیر توجه کنید:
<html>
<body>
<div id="A">I am the #A element</div>
</body>
</html>
document.getElementById('A').addEventListener(
'click',
function (e) {
console.log('When #A is clicked, I shall run first!');
},
false,
);
document.getElementById('A').addEventListener(
'click',
function (e) {
console.log('When #A is clicked, I shall run second!');
e.stopImmediatePropagation();
},
false,
);
document.getElementById('A').addEventListener(
'click',
function (e) {
console.log('When #A is clicked, I would have run third, if not for stopImmediatePropagation');
},
false,
);
مثال فوق منجر به خروجی کنسول زیر خواهد شد:
"When #A is clicked, I shall run first!"
"When #A is clicked, I shall run second!"
توجه داشته باشید که دستیار سومین رویداد هرگز به دلیل این واقعیت که کنترل کننده رویداد دوم e.stopImmediatePropagation()
را صدا نمی کند ، هرگز اجرا نمی شود. اگر در عوض به نام e.stopPropagation()
خوانده می شدیم ، کنترل کننده سوم هنوز اجرا می شود.
event.preventDefault()
If stopPropagation()
prevents an event from traveling "downwards" (capturing) or "upwards" (bubbling), what then, does preventDefault()
do? It sounds like it does something similar. آیا آن را انجام می دهد؟
نه واقعا. While the two are often confused, they actually don't have much to do with each other. When you see preventDefault()
, in your head, add the word "action." Think "prevent the default action."
And what is the default action you may ask? Unfortunately, the answer to that isn't quite as clear because it's highly dependent on the element + event combination in question. And to make matters even more confusing, sometimes there is no default action at all!
Let's begin with a very simple example to understand. What do you expect to happen when you click a link on a web page? Obviously, you expect the browser to navigate to the URL specified by that link. In this case, the element is an anchor tag and the event is a click event. That combination ( <a>
+ click
) has a "default action" of navigating to the link's href. What if you wanted to prevent the browser from performing that default action? That is, suppose you want to prevent the browser from navigating to the URL specified by the <a>
element's href
attribute? This is what preventDefault()
will do for you. این مثال را در نظر بگیرید:
<a id="avett" href="https://www.theavettbrothers.com/welcome">The Avett Brothers</a>
document.getElementById('avett').addEventListener(
'click',
function (e) {
e.preventDefault();
console.log('Maybe we should just play some of their music right here instead?');
},
false,
);
You can interactively play with this in the live demo below. Click the link The Avett Brothers and observe the console output (and the fact that you are not redirected to the Avett Brothers website).
Normally, clicking the link labelled The Avett Brothers would result in browsing to www.theavettbrothers.com
. In this case though, we've wired up a click event handler to the <a>
element and specified that the default action should be prevented. Thus, when a user clicks this link, they won't be navigated anywhere, and instead the console will simply log "Maybe we should just play some of their music right here instead?"
What other element/event combinations allow you to prevent the default action? I cannot possibly list them all, and sometimes you have to just experiment to see. But briefly, here are a few:
<form>
element + "submit" event:preventDefault()
for this combination will prevent a form from submitting. This is useful if you want to perform validation and should something fail, you can conditionally call preventDefault to stop the form from submitting.<a>
element + "click" event:preventDefault()
for this combination prevents the browser from navigating to the URL specified in the<a>
element's href attribute.document
+ "mousewheel" event:preventDefault()
for this combination prevents page scrolling with the mousewheel (scrolling with keyboard would still work though).
↜ This requires callingaddEventListener()
with{ passive: false }
.document
+ "keydown" event:preventDefault()
for this combination is lethal. It renders the page largely useless, preventing keyboard scrolling, tabbing, and keyboard highlighting.document
+ "mousedown" event:preventDefault()
for this combination will prevent text highlighting with the mouse and any other "default" action that one would invoke with a mouse down.<input>
element + "keypress" event:preventDefault()
for this combination will prevent characters typed by the user from reaching the input element (but don't do this; there is rarely, if ever, a valid reason for it).document
+ "contextmenu" event:preventDefault()
for this combination prevents the native browser context menu from appearing when a user right-clicks or long-presses (or any other way in which a context menu might appear).
This is not an exhaustive list by any means, but hopefully it gives you a good idea of how preventDefault()
can be used.
A fun practical joke?
What happens if you stopPropagation()
and preventDefault()
in the capturing phase, starting at the document? شوخ طبعی به وجود می آید! The following code snippet will render any web page just about completely useless:
function preventEverything(e) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
}
document.addEventListener('click', preventEverything, true);
document.addEventListener('keydown', preventEverything, true);
document.addEventListener('mousedown', preventEverything, true);
document.addEventListener('contextmenu', preventEverything, true);
document.addEventListener('mousewheel', preventEverything, { capture: true, passive: false });
I don't really know why you'd ever want to do this (except maybe to play a joke on someone), but it is useful to think about what's happening here, and realize why it creates the situation it does.
All events originate at window
, so in this snippet, we're stopping, dead in their tracks, all click
, keydown
, mousedown
, contextmenu
, and mousewheel
events from ever getting to any elements that might be listening for them. We also call stopImmediatePropagation
so that any handlers wired up to the document after this one, will be thwarted as well.
Note that stopPropagation()
and stopImmediatePropagation()
aren't (at least not mostly) what render the page useless. They simply prevent events from getting where they would otherwise go.
But we also call preventDefault()
, which you'll recall prevents the default action . So any default actions (like mousewheel scroll, keyboard scroll or highlight or tabbing, link clicking, context menu display, etc.) are all prevented, thus leaving the page in a fairly useless state.
دموهای زنده
To explore all the examples from this article again in one place, check out the embedded demo below.
قدردانی
Hero image by Tom Wilson on Unsplash .