مقدمه‌ ای بر رویدادها در جاوا اسکریپت — راهنمای کاربردی

۳۰۴ بازدید
آخرین به‌روزرسانی: ۰۸ شهریور ۱۴۰۲
زمان مطالعه: ۲۰ دقیقه
مقدمه‌ ای بر رویدادها در جاوا اسکریپت — راهنمای کاربردی

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

پیش‌نیازها

  • سواد مقدماتی رایانه
  • درکی ابتدایی از HTML و CSS
  • آشنایی مقدماتی با جاوا اسکریپت

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

مقدمه‌ای بر رویدادها

همان طور که پیش‌تر اشاره کردیم، «رویدادها» (Events) اقدام‌ها یا رخدادهایی هستند که در سیستمی که برایش برنامه‌نویسی می‌کنیم، پیش می‌آیند و سیستم در زمان رخداد این حالت، نوعی سیگنال ارسال می‌کند و ساز و کاری ارائه می‌دهد که می‌توان آن رویداد را به صورت خودکار دریافت کرد. برای مثال در زمان اتفاق افتادن آن رویداد نوعی کد اجرا می‌شود. برای مثال در یک فرودگاه زمانی که باند برای پرواز یک هواپیما باز می‌شود، یک سیگنال به خلبان ارسال می‌شود و در نتیجه وی می‌تواند هواپیما را به پرواز دربیاورد.

رویدادها

در مورد وب، رویدادها درون پنجره مرورگر ایجاد می‌شوند و در اغلب موارد به آیتم خاصی که به آن مربوط است الصاق شده‌اند. در نتیجه رویداد می‌تواند متعلق به یک عنصر منفرد باشد و یا به مجموعه‌ای از عناصر تعلق داشته باشد. همچنین رویداد می‌تواند به سند بارگذاری شده در برگه جاری و یا کل پنجره مرورگر مربوط باشد. انواع بسیار مختلفی از رویدادها هستند که می‌توانند رخ دهند. در ادامه مثال‌هایی از برخی رویدادها را ارائه کرده‌ایم:

  • کاربر روی یک عنصر خاص کلیک می‌کند یا کرسر را روی یک عنصر می‌برد.
  • کاربر یک کلید را روی صفحه کلید می‌فشارد.
  • کاربر پنجره مرورگر را تغییر اندازه داده یا آن را می‌بندد.
  • مراحل بارگذاری یک صفحه وب پایان می‌پذیرد.
  • یک فرم تحویل داده می‌شود.
  • یک ویدئو پخش یا متوقف می‌شود و یا به پایان می‌رسد.
  • یک خطا رخ می‌دهد.

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

هر رویداد دارای یک «دستگیره رویداد» (Event handler) مربوطه است که در واقع یک بلوک کد به حساب می‌آید. این بلوک کد که در اغلب موارد تابع جاوا اسکریپت تعریف شده از سوی کاربر است، در زمان اتفاق افتادن رویداد اجرا می‌شود. هنگامی که چنین بلوک کدی را چنان تعریف کنیم که در پاسخ به اتفاق افتادن یک رویداد اجرا شود، گفته می‌شود که یک دستگیره رویداد ثبت شده است. دقت کنید که دستگیره‌های رویداد در برخی موارد «شنونده رویداد» (Event Listener) نیز نامیده می‌شوند. این دو نام با توجه به مقاصدی که ما دنبال می‌کنیم به جای هم قابل استفاده هستند؛ اما اگر بخواهیم روشن‌تر صحبت کنیم این دو با یکدیگر همکاری دارند. شنونده منتظر اتفاق افتادن یک رویداد است و دستگیره، کدی است که در پاسخ به اتفاق افتادن رویداد اجرا می‌شود.

نکته: لازم به ذکر است که رویدادهای وب بخشی از هسته اصلی زبان جاوا اسکریپت نیستند و به عنوان بخشی از API-های جاوا اسکریپت در مرورگر تعریف می‌شوند.

یک مثال ساده

در ادامه یک مثال ساده را مورد بررسی قرار می‌دهیم، تا آن چه را تا به اینجا آموخته‌ایم مرور کنیم. ما در بخش‌های قبلی این سری مطالب آموزشی با رویدادها و دستگیره‌های رویداد در مثال‌های فراوانی آشنا شده‌ایم؛ اما جهت مرور دوباره یک مثال دیگر را نیز بررسی می‌کنیم. در کد زیر یک عنصر <button> داریم که وقتی فشرده شود، پس‌زمینه صفحه به یک رنگ تصادفی تغییر می‌یابد:

1<button>Change color</button>

کد جاوا اسکریپت به صورت زیر است:

1var btn = document.querySelector('button');
2
3function random(number) {
4  return Math.floor(Math.random()*(number+1));
5}
6
7btn.onclick = function() {
8  var rndCol = 'rgb(' + random(255) + ',' + random(255) + ',' + random(255) + ')';
9  document.body.style.backgroundColor = rndCol;
10}

ما در این کد با استفاده از تابع ()Document.querySelector یک ارجاع به دکمه درون یک متغیر ایجاد می‌کنیم که btn نام دارد. همچنین یک تابع تعریف می‌کنیم که یک عدد تصادفی بازگشت می‌دهد. بخش سوم کد همان دستگیره رویداد است. متغیر btn به یک عنصر <button> اشاره دارد و این نوع شیء، چند نوع رویداد دارد که می‌تواند روی آن‌ها اجرا شود. از این رو دستگیره‌های رویدادی ارائه شده‌اند. ما منتظریم که یک رویداد کلیک شدن رخ دهد و به این منظور خصوصیت دستگیره رویداد onclick را برابر با یک «تابع بی‌نام» (anonymous function) قرار می‌دهیم که شامل کدی است که یک رنگ تصادفی RGB تولید کرده و آن را به صورت رنگ پس‌زمینه <body> قرار می‌دهد.

این کد هر زمان که رویداد کلیک روی عنصر <button> رخ دهد، یعنی هنگامی که کاربر روی آن کلیک کند، اجرا خواهد شد.

رویدادها صرفاً در مورد صفحه‌های وب نیستند

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

برای نمونه Node.js یک محیط اجرای بسیار محبوب جاوا اسکریپت است که به توسعه‌دهندگان امکان می‌دهد تا از جاوا اسکریپت برای ساختن اپلیکیشن‌های شبکه و سمت سرور استفاده کنند. مدل رویداد Node.js روی شنونده‌ها برای دریافت رویدادها و روی emitter-ها برای ارسال دوره‌ای رویدادها تکیه دارد. با این که این وضعیت چندان متفاوت به نظر نمی‌رسد؛ اما در باطن کاملاً طرز کار متفاوتی دارد و از تابع‌هایی مانند ()on جهت ثبت به عنوان شنونده رویداد و ()once برای ثبت یک شنونده رویداد که پس از اجرای اولیه ثبت می‌شود، بهره گرفته شده است. برای کسب اطلاعات بیشتر و مشاهده مثال‌های عملی، می‌توانید به مستندات رویداد connect در HTTP (+) مراجعه کنید.

به عنوان یک مثال دیگر، می‌توانید از جاوا اسکریپت برای ساخت افزونه‌های چند مرورگری، بهینه‌سازی کارکرد مرورگر با استفاده از یک فناوری به نام WebExtensions (+) بهره بگیرید. مدل رویداد آن مشابه مدل رویدادهای وب؛ اما اندکی متفاوت است، چون مشخصات شنونده‌های رویداد به صورت «حالت شتری» (camel-cased) نوشته می‌شوند یعنی به جای onmessage از onMessage استفاده می‌شود و باید با تابع addListener ترکیب شوند. برای مشاهده مثال‌های بیشتر به صفحه runtime.onMessage (+) مراجعه کنید.

در این مرحله لازم نیست که در مورد محیط‌های دیگر اطلاعاتی داشته باشید و هدف از بخش فوق این است که برای شما روشن کنیم که رویدادها در محیط‌های برنامه‌نویسی مختلف می‌توانند طرز کار متفاوتی داشته باشند.

روش‌های استفاده از رویدادهای وب

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

مشخصات Event Handler

مشخصات دستگیره‌های رویداد جهت نوشتن کد دستگیره رویدادی طراحی شده‌اند که در طی این دوره آموزشی از ابتدا تاکنون بارها مشاهده کرده‌ایم. اگر به مثال ابتدای این مطلب بازگردیم:

1var btn = document.querySelector('button');
2
3btn.onclick = function() {
4  var rndCol = 'rgb(' + random(255) + ',' + random(255) + ',' + random(255) + ')';
5  document.body.style.backgroundColor = rndCol;
6}

مشخصه onclick یک مشخصه دستگیره رویداد است که در این موقعیت‌ها استفاده می‌شود. این یک مشخصه ضروری مانند همه مشخصه‌های دیگر موجود برای یک دکمه مانند btn.textContent یا btn.style است؛ اما نوع آن خاص است، زیرا وقتی که آن را برابر با کدی قرار دهیم، این کد هر زمان که رویداد کلیک شدن دکمه اتفاق بیفتد، اجرا خواهد شد.

همچنین می‌توان مشخصه دستگیره را برابر با یک تابع بانام قرار داد. کد زیر همان کاری را انجام می‌دهد که مثال قبلی اجرا می‌کند:

1var btn = document.querySelector('button');
2
3function bgChange() {
4  var rndCol = 'rgb(' + random(255) + ',' + random(255) + ',' + random(255) + ')';
5  document.body.style.backgroundColor = rndCol;
6}
7
8btn.onclick = bgChange;

مشخصات دستگیره برای رویدادهای بسیار فراوانی وجود دارند که در ادامه آن‌ها را مورد بررسی قرار می‌دهیم. قبل از هر چیز یک کپی محلی از فایل زیر روی سیستم خود ایجاد کرده و آن را در یک مرورگر باز کنید:

1<!DOCTYPE html>
2<html>
3  <head>
4    <meta charset="utf-8">
5    <title>Random color example — event handler property</title>
6    <style>
7      button {
8        margin: 10px;
9      }
10    </style>
11  </head>
12  <body>
13    <button>Change color</button>
14    <script>
15      var btn = document.querySelector('button');
16      function random(number) {
17        return Math.floor(Math.random()*number);
18      }
19      function bgChange() {
20        var rndCol = 'rgb(' + random(255) + ',' + random(255) + ',' + random(255) + ')';
21        document.body.style.backgroundColor = rndCol;
22      }
23      btn.onclick = bgChange;    
24    </script>
25  </body>
26</html>

این صرفاً یک کپی از مثال ایجاد رنگ‌های تصادفی است که قبلاً برسی کردیم. اینک تلاش می‌کنیم مشخصه btn.onclick را به ترتیب به مقادیر مختلف زیر تغییر دهیم و نتیجه هر کدام را ملاحظه کنیم:

  • btn.onfocus و btn.onblur: این کد زمانی که دکمه فوکوس را دریافت می‌کند یا از دست می‌دهد اجرا می‌شود. شما می‌توانید با زدن مکرر دکمه tab فوکوس صفحه را به این دکمه برده و یا از آن جدا کنید. از این مشخصه عموماً برای نمایش اطلاعاتی در مورد شیوه پر کردن فیلدهای فرم در زمان دریافت فوکوس و یا نمایش یک پیام در صورت وارد کردن یک مقدار نادرست در فیلدهای فرم استفاده می‌شود.
  • btn.ondblclick: رنگ صفحه تنها زمانی تغییر می‌یابد که روی آن دو بار کلیک شود.
  • window.onkeypress، window.onkeydown، window.onkeyup: در این مثال رنگ صفحه زمانی تغییر خواهد یافت که یک کلید روی صفحه کلید زده شود. keypress به فشرده شدن هر نوع کلید (یعنی فشردن به سمت پایین و رها کردن کلید) اشاره می‌کند؛ در حالی که keydown اشاره به صرفاً پایین رفتن یک کلید و keyup نیز اشاره به رها کردن یک کلید صفحه کلید دارد. دقت کنید که اگر تلاش کنید دستگیره رویداد را روی خود دکمه ثبت کنید، این مثال کار نمی‌کند، چون باید آن را روی شیء window ثبت کنید که کل پنجره مرورگر را شامل می‌شود.
  • btn.onmouseover و btn.onmouseout: در این مثال رنگ صفحه زمانی تغییر می‌یابد که اشاره‌گر ماوس طوری جابجا شود که روی دکمه قرار گیرد و یا در مورد onmouseout زمانی فعال می‌شود که ماوس از محدوده دکمه خارج شود.

برخی رویدادها بسیار عمومی هستند و تقریباً در همه جا حضور دارند. برای نمونه دستگیره onclick می‌تواند تقریباً روی هر عنصری ثبت شود؛ در حالی که برخی دیگر خاص‌تر هستند و تنها در موقعیت‌های خاص به کار می‌آیند. برای مثال می‌توان از onplay تنها روی عناصر خاصی مانند <video> استفاده کرد.

دستگیره‌های رویداد درون‌خطی (که نباید استفاده شوند)

شما احتمالاً در کد خود با الگویی مانند زیر مواجه شدید:

1<button onclick="bgChange()">Press me</button>
1function bgChange() {
2  var rndCol = 'rgb(' + random(255) + ',' + random(255) + ',' + random(255) + ')';
3  document.body.style.backgroundColor = rndCol;
4}

نخستین متد برای ثبت دستگیره‌های رویداد که در وب ارائه شد، شامل خصوصیت‌های HTML دستگیره رویداد، یعنی «دستگیره‌های رویداد درون‌خطی» (inline event handlers) بود که در مثال فوق مشاهده کردید. مقدار این خصوصیت در عمل کد جاوا اسکریپتی بود که باید در زمان اتفاق افتادن رویداد اجرا می‌شد. مثال فوق یک تابع را فراخوانی می‌کند که درون یک عنصر <script> روی همان صفحه تعریف شده است؛ اما می‌توان برای مثال به صورت زیر کد جاوا اسکریپت را مستقیماً درون خصوصیت قرار داد:

1<button onclick="alert('Hello، this is my old-fashioned event handler!');">Press me</button>

شما با کمی بررسی متوجه می‌شوید که بسیاری از خصوصیت‌های HTML معادل مشخصه‌های دستگیره رویداد هستند؛ اما نباید از آن‌ها استفاده کنید، چون در حال حاضر استفاده از آن‌ها رویه نامناسبی تلقی می‌شود. با این که استفاده از این خصوصیت‌های رویداد در مواردی که می‌خواهید کاری را به سرعت انجام دهید آسان به نظر می‌آید؛ اما این وضعیت به سرعت به موقعیتی غیر قابل مدیریت و ناکارآمد تبدیل خواهد شد.

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

استفاده از دستگیره‌های رویداد درون‌خطی حتی در یک فایل منفرد نیز ایده مناسبی محسوب نمی‌شود. در مورد یک دکمه شاید مشکلی پیش نیاید، اما اگر 100 دکمه باشد چطور؟ شما باید 100 خصوصیت را به فایل خود اضافه کنید و این وضعیت به سرعت به یک کابوس «نگهداری» (maintenance) تبدیل می‌شود. در جاوا اسکریپت می‌توان به سادگی یک تابع دستگیره رویداد را به همه دکمه‌های صفحه اضافه کرد و مهم نیست که چه تعداد دکمه وجود دارد. روش کار به صورت زیر است:

1var buttons = document.querySelectorAll('button');
2
3for (var i = 0; i < buttons.length; i++) {
4  buttons[i].onclick = bgChange;
5}

نکته: جداسازی منطق برنامه‌نویسی از محتوا همچنین موجب می‌شود که سایت شما برای موتورهای جستجو مناسب‌تر باشد.

()addEventListener و ()removeEventListener

جدیدترین نوع ساز و کار رویداد که در بخش رویدادهای سطح 2 مدل شیء سند (+) تعریف شده است، تابع جدیدی در اختیار مرورگرها قرار داده است که ()addEventListener نام دارد. این تابع به روشی مشابه مشخصات دستگیره رویداد عمل می‌کند؛ اما ساختار آن متفاوت است. ما می‌توانیم مثال تغییر تصادفی رنگ قبلی خودمان را به صورت زیر بازنویسی کنیم:

1var btn = document.querySelector('button');
2
3function bgChange() {
4  var rndCol = 'rgb(' + random(255) + ',' + random(255) + ',' + random(255) + ')';
5  document.body.style.backgroundColor = rndCol;
6}   
7
8btn.addEventListener('click', bgChange);

درون تابع ()addEventListener دو پارامتر را تعیین کرده‌ایم که یکی نام رویدادی است که می‌خواهیم دستگیره را برای آن ثبت کنیم و دیگری کدی است که تابع دستگیره‌ای که می‌خواهیم در پاسخ به آن اجرا کنیم را شامل می‌شود. دقت کنید که بهتر است همه کد درون تابع ()addEventListener را در یک تابع بی‌نام به صورت زیر قرار دهیم:

1btn.addEventListener('click', function() {
2  var rndCol = 'rgb(' + random(255) + ',' + random(255) + ',' + random(255) + ')';
3  document.body.style.backgroundColor = rndCol;
4});

این ساز و کار برخی مزیت‌ها نسبت به سازو کارهای بررسی شده قبلی دارد. در آغاز باید اشاره کنیم که یک تابع همتا به نام ()removeEventListener وجود دارد که وظیفه حذف شنونده‌ای که قبلاً اضافه شده را بر عهده دارد. برای نمونه این تابع می‌تواند شنونده‌ای را که در بلوک کد نخست این بخش اضافه شده بود حذف کند:

1btn.removeEventListener('click'، bgChange);

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

نکته دوم این است که می‌توانید چندین دستگیره را برای شنونده یکسانی ثبت کنید. دو دستگیره زیر اعمال نخواهند شد:

1myElement.onclick = functionA;
2
3myElement.onclick = functionB;

چون خط دوم مقدار onclick تعیین شده در خط اول را بازنویسی می‌کند. با این وجود در کد زیر:

1myElement.addEventListener('click'، functionA);
2myElement.addEventListener('click'، functionB);

هر دو تابع هنگام کلیک شدن عنصر اجرا می‌شوند.

علاوه بر آن چندین ویژگی و گزینه قدرتمند دیگر از سوی ساز و کار رویداد ارائه شده است. بررسی این ویژگی‌ها خارج از حیطه این مقاله است؛ اما اگر می‌خواهید در این مورد بیشتر بدانید، می‌توانید به صفحه مرجع تابع‌های ()addEventListener (+) و ()removeEventListener (+) مراجعه کنید.

از چه ساز و کاری باید استفاده کنیم؟

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

دو ساز و کار دیگر، دست‌کم در کاربردهای ساده تا حدودی قابل تعویض با هم هستند:

  • مشخصات دستگیره رویداد، قدرت و گزینه‌های کمتری دارد؛ اما تطبیق‌پذیری آن بین مرورگرهای مختلف بهتر است، چون حتی یک مرورگر قدیمی مانند Internet Explorer 8 نیز آن را پشتیبانی می‌کند. در ابتدای کار بهتر است کار خود را با آن‌ها آغاز کنید.
  • رویدادهای DOM LEVEL 2 (یعنی addEventListener و غیره) قدرت بیشتری دارند؛ اما ممکن است پیچیده‌تر نیز باشند و پشتیبانی از آن‌ها به خوبی گزینه قبلی نباشد. برای نمونه تنها از Internet Explorer 9 به بعد از آن پشتیبانی می‌کند. البته شما باید این گزینه را نیز بیاموزید و در موارد مقتضی از آن استفاده کنید.

مزیت اصلی ساز و کار سوم در فهرست فوق این است که می‌تواند در صورت نیاز کد دستگیره رویداد را با استفاده از ()removeEventListener حذف کرد. همچنین می‌توان چندین شونده برای نوع یکسانی از عناصر در صورت نیاز افزود. برای نمونه می‌توان تابع زیر را روی یک عنصر به دفعات مکرر با تابع‌های مختلفی که در آرگومان دوم تعیین شده‌اند فراخوانی کرد:

1addEventListener('click'، function() { ... })

این کار در مشخصات رویداد امکان‌پذیر نیست، زیرا تلاش‌های بعدی برای تعیین مشخصه موجب بازنویسی انواع قبلی می‌شوند یعنی:

1element.onclick = function1;
2element.onclick = function2;
3etc.

نکته: اگر شما مجبور باشید در پروژه خود از مرورگرهای قدیمی‌تر از Internet Explorer 8 نیز پشتیبانی کنید، ممکن است با دشواری‌هایی مواجه شوید، چون مرورگرهای خیلی قدیمی مدل‌های رویداد متفاوتی نسبت به مرورگرهای جدیدتر دارند. اما جای ترس نیست، زیرا اغلب کتابخانه‌های جاوا اسکریپت تابع‌های داخلی دارند که این تفاوت‌های بین مرورگرها را انتزاع می‌کنند. در هر صورت در این مرحله از مسیر یادگیری‌تان نباید در این خصوص نگران باشید.

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

در این بخش به طور خلاصه در مورد برخی مفاهیم پیشرفته‌تر که به رویدادها مربوط هستند صحبت خواهیم کرد. البته باید توجه داشته باشید که درک کامل این مفاهیم در این مرحله ضرورت ندارد؛ اما با یادگیری این مفاهیم بهتر می‌توانید برخی الگوهای کدنویسی را که گاه به گاه در ادامه مراحل مشاهده خواهید کرد درک کنید.

شیء رویداد

برخی اوقات ممکن است درون یک تابع دستگیره رویداد با پارامتری مواجه شوید که دارای نامی مانند event ،evt یا صرفاً e است. این پارامتر، شیء رویداد نام دارد و به صوت خودکار به دستگیره‌های رویداد ارسال می‌شود تا ویژگی‌ها و اطلاعات بیشتری در اختیار آن قرار دهد. برای نمونه ما می‌توانیم مثال قبلی خودمان در مورد تغییر رنگ تصادفی را با اندکی تفاوت به صورت زیر بازنویسی کنیم:

1function bgChange(e) {
2  var rndCol = 'rgb(' + random(255) + ',' + random(255) + ',' + random(255) + ')';
3  e.target.style.backgroundColor = rndCol;
4  console.log(e);
5}  
6
7btn.addEventListener('click', bgChange);

در این کد می‌بینید که از یک شیء رویداد یعنی e در تابع استفاده شده است و در تابعی که رنگ پس‌زمینه را تعیین می‌کند یعنی e.target از آن بهره گرفته‌ایم. مشخصه target برای شیء رویداد همواره یک ارجاع به عنصری است که رویداد در مورد آن اتفاق افتاده است. از این رو در این مثال ما در حال تعیین رنگ پس‌زمینه تصادفی برای یک دکمه هستیم و نه برای کل صفحه.

نکته: شما می‌توانید از نامی که دوست دارید برای شیء رویداد استفاده کنید. تنها باید دقت کنید که می‌توانید از آن برای ارجاع درون تابع دستگیره رویداد استفاده کنید. e ،evt یا event نام‌هایی هستند که به طور معمول از سوی توسعه‌دهندگان استفاده می‌شوند، زیرا کوتاه هستند و به‌خاطرسپاری آن‌ها آسان‌تر است. بنابراین بهتر است شما نیز از این استاندارد پیروی کنید.

e.target در مواردی که بخواهید دستگیره رویداد یکسانی را روی چندین عنصر تعیین کنید تا در صورت بروز رویدادی روی آن‌ها کار یکسانی اجرا شود به طرز خارق‌العاده‌ای کارآمد خواهد بود. در این حالت برای مثال می‌توان مجموعه‌ای از 16 کادر مختلف داشت که وقتی روی هر کدام کلیک می‌شود، ناپدید می‌شوند. بدین ترتیب کافی است روش ناپدید شدن هر کادر را صرفاً روی e.target تعیین کنیم و دیگر نیاز نیست آن کادر به خصوص را به روشی دشوار انتخاب نماییم. در مثال زیر ما 16 عنصر <div> با استفاده از جاوا اسکریپت ساخته‌ایم. سپس همه آن‌ها را با استفاده از ()document.querySelectorAll انتخاب کرده و روی همه آن‌ها حلقه‌ای تعریف می‌کنیم و دستگیره onclick را به هر یک از آن‌ها انتساب می‌دهیم. بدین ترتیب زمانی که روی هر کدام از آن‌ها کلیک شود یک رنگ تصادفی به آن تعلق می‌گیرد:

1var divs = document.querySelectorAll('div');
2
3for (var i = 0; i < divs.length; i++) {
4  divs[i].onclick = function(e) {
5    e.target.style.backgroundColor = bgChange();
6  }
7}

کد کامل این مثال با در نظر گرفتن HTML به صورت زیر است که می‌توانید آن را روی فایلی در سیستم خود کپی کرده و به صورت آفلاین در مرورگر باز کرده و مورد بررسی قرار دهید:

1<!DOCTYPE html>
2<html>
3  <head>
4    <meta charset="utf-8">
5    <title>Useful event target example</title>
6    <style>
7      div {
8        background-color: red;
9        height: 100px;
10        width: 25%;
11        float: left;
12      }
13    </style>
14  </head>
15  <body>
16    <script>
17      for(var i = 1; i <= 16; i++) {
18        var myDiv = document.createElement('div');
19        document.body.appendChild(myDiv);
20      }
21      function random(number) {
22        return Math.floor(Math.random()*number);
23      }
24      function bgChange() {
25        var rndCol = 'rgb(' + random(255) + ',' + random(255) + ',' + random(255) + ')';
26        return rndCol;
27      }
28      var divs = document.querySelectorAll('div');
29      for(var i = 0; i < divs.length; i++) {
30        divs[i].onclick = function(e) {
31          e.target.style.backgroundColor = bgChange();
32        }
33      }
34    </script>
35  </body>
36</html>

خروجی مثال فوق به صورت زیر است. می‌توانید در بخش‌های مختلف آن کلیک کنید، همانطور که می‌بینید کاملاً سرگرم‌کننده است:

اغلب دستگیره‌های رویداد که با آن‌ها سر و کار داریم صرفاً مجموعه استانداردی از مشخصات و تابع‌ها (متدها) برای شیء رویداد دارند. اما برخی دستگیره‌های رویداد پیشرفته‌تر، شامل مشخصات تخصصی‌تری هستند که به داده‌های بیشتری نیاز دارد. برای مثال «API ضبط رسانه» (Media Recorder API) یک رویداد dataavailable دارد که وقتی صدا یا ویدئویی ضبط می‌شود به کار می‌آید و برای انجام کاری در این خصوص استفاده می‌شود. برای نمونه می‌توان فایل مربوطه را ذخیره یا بازپخش کرد. دستگیره رویدادهای متناظر ondataavailable دارای یک مشخصه data است که شامل داده‌های صوت یا ویدئوی ضبط شده است و می‌توان به این وسیله به آن دسترسی یافته و هر کاری روی آن اجرا کرد.

جلوگیری از رفتار پیش‌فرض

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

مشکل این وضعیت آن است که ممکن است کاربر داده‌هایی را به طور نادرست وارد کرده باشد. شما به عنوان یک توسعه‌دهنده باید به کاربران بگویید که اشتباه کرده‌اند و برای اصلاح خطای خود باید چه کاری بکنند. برخی مرورگرها از ویژگی‌های اعتبارسنجی خودکار داده‌ها پشتیبانی می‌کنند؛ اما از آنجا که برخی دیگر جنین ویژگی‌هایی ندارند توصیه می‌کنیم که شما روی این قابلیت‌ها تکیه نکنید و بررسی‌های اعتبارسنجی خود را به عمل بیاورید. به مثال ساده زیر توجه کنید.

در ابتدا یک فرم HTML ساده وجود دارد که از شما می‌خواهد نام و نام خانوادگی خود را وارد کنید:

1<form>
2  <div>
3    <label for="fname">First name: </label>
4    <input id="fname" type="text">
5  </div>
6  <div>
7    <label for="lname">Last name: </label>
8    <input id="lname" type="text">
9  </div>
10  <div>
11     <input id="submit" type="submit">
12  </div>
13</form>
14<p></p>

اینک مقداری کدهای جاوا اسکریپت می‌نویسیم که به پیاده‌سازی یک بررسی بسیار ابتدایی درون دستگیره رویداد onsubmit می‌پردازد. رویداد submit فرم زمانی اجرا می‌شود که آن فرم تحویل داده شود. در این موارد تابع ()preventDefault روی شیء رویداد فراخوانی می‌شود که موجب توقف فرایند تحویل می‌شود و سپس یک پیام خطا در پاراگراف زیر فرم نمایش می‌یابد که به کاربر اعلام می‌کند خطایی رخ داده است:

1var form = document.querySelector('form');
2var fname = document.getElementById('fname');
3var lname = document.getElementById('lname');
4var submit = document.getElementById('submit');
5var para = document.querySelector('p');
6
7form.onsubmit = function(e) {
8  if (fname.value === '' || lname.value === '') {
9    e.preventDefault();
10    para.textContent = 'You need to fill in both names!';
11  }
12}

بدیهی است که این یک اعتبارسنجی ضعیف برای فرم محسوب می‌شود، چون در مواردی که صرفاً کاراکترهای فاصله یا عدد در فیلدها وارد شده باشند از اعتبارسنجی آن جلوگیری نمی‌کند؛ اما به منظور مقاصد آموزشی مناسب است. خروجی کار چنین است:

Bubbling  و Capture برای رویداد

بخش نهایی که قصد داریم بررسی کنیم چیزی است که ممکن است زیاد با آن مواجه نشوید؛ اما اگر آن را درک نکنید واقعاً به دردسر خواهید افتاد. Bubbling و capture رویداد دو ساز و کار متفاوت هستند که وقتی دو دستگیره رویداد یکسانی روی عنصر واحدی فعال می‌شوند به توصیف آن چه رخ می‌دهد می‌پردازند. به مثال زیر توجه کنید تا قضیه روشن‌تر شود:

این یک مثال بسیار ساده است که یک عنصر <div> و <video> درون آن را نمایش داده یا پنهان می‌سازد:

1<!DOCTYPE html>
2<html>
3  <head>
4    <meta charset="utf-8">
5    <title>Show video box example</title>
6    <style>
7      div {
8        position: absolute;
9        top: 50%;
10        transform: translate(-50%,-50%);
11        width: 480px;
12        height: 380px;
13        border-radius: 10px;
14        background-color: #eee;
15        background-image: linear-gradient(to bottom, rgba(0,0,0,0), rgba(0,0,0,0.1));
16      }
17      .hidden {
18        left: -50%;
19      }
20      .showing {
21        left: 50%;
22      }
23      div video {
24        display: block;
25        width: 400px;
26        margin: 40px auto;
27      }
28    </style>
29  </head>
30  <body>
31    <button>Display video</button>
32
33    <div class="hidden">
34      <video>
35        <source src="rabbit320.mp4" type="video/mp4">
36        <source src="rabbit320.webm" type="video/webm">
37        <p>Your browser doesn't support HTML5 video. Here is a <a href="rabbit320.mp4">link to the video</a> instead.</p> 
38      </video>
39    </div>
40
41    <script>
42      var btn = document.querySelector('button');
43      var videoBox = document.querySelector('div');
44      var video = document.querySelector('video');
45      btn.onclick = function() {
46        videoBox.setAttribute('class','showing');
47      }
48      videoBox.onclick = function() {
49        videoBox.setAttribute('class','hidden');
50      };
51      video.onclick = function() {
52        video.play();
53      };
54      
55    </script>
56  </body>
57</html>

زمانی که عنصر <button> کلیک شود، ویدئو نمایش می‌یابد و با تغییر دادن خصوصیت کلاس روی <div> از hidden به showing می‌توانید حالت نمایش آن را تغییر دهید. توجه داشته باشید که بخش CSS مثال شامل دو کلاس است که به ترتیب کادر مربوطه را روی صفحه قرار داده یا حذف می‌کنند:

1btn.onclick = function() {
2  videoBox.setAttribute('class', 'showing');
3}

سپس چند دستگیره رویداد اضافه شده است که دستگیره اول به <div> تعلق دارد و دستگیره دوم به <video> مربوط است. ایده کار چنین است که وقتی ناحیه خارج از <div> ویدئو کلیک شود، این ویدئو باید مجدداً پنهان شود و زمانی که روی خود ویدئو کلیک شود، ویدئو شروع به پخش می‌کند.

1videoBox.onclick = function() {
2  videoBox.setAttribute('class', 'hidden');
3};
4
5video.onclick = function() {
6  video.play();
7};

اما مشکلی وجود دارد. در حال حاضر وقتی روی ویدئو کلیک کنید شروع به پخش می‌کند؛ اما موجب می‌شود که <div> نیز همزمان پنهان شود. دلیل این مسئله آن است که ویدئو درون <div> قرار دارد و بخشی از آن است. بنابراین با بررسی کلیک روی ویدئو در واقع هر دو دستگیره رویداد اجرا می‌شوند.

توضیح Bubbling و Capturing

زمانی که یک رویداد روی عنصری اجرا شود که عناصر والدی دارد، مرورگرهای مدرن دو مرحله متفاوت را اجرا می‌کنند که یکی capturing و دیگری bubbling نام دارند. در مرحله capturing اتفاقات زیر رخ می‌دهند:

  • مرورگر بررسی می‌کند که آیا دورترین جد عنصر یعنی <html> دارای دستگیره رویداد onclick ثبت شده برای مرحله capturing است یا نه و در صورتی که چنین باشد آن را اجرا می‌کند.
  • سپس به عنصر بعدی درون <html> می‌رود و بررسی می‌کند که آیا دستگیره رویداد capturing دارد یا نه و در صورت وجود آن را اجرا می‌کند و همین کار را تا آخر تکرار می‌کند تا این که به عنصری برسد که در عمل روی آن کلیک کرده‌ایم.

در مرحله Bubbling دقیقاً متضاد این وضعیت رخ می‌دهد:

  • مرورگر بررسی می‌کند که آیا عنصری که روی آن کلیک شده است دارای دستگیره رویداد onclick ثبت شده برای مرحله Bubbling است یا نه و در صورتی که چنین باشد اجرا می‌کند.
  • سپس به والد بلافصل عنصر مراجعه کرده و همان کار را تکرار می‌کند و همین طور به عناصر بالاتر می‌رود تا این که به <html> برسد.

رویدادها

در مرورگرهای مدرن به طور پیش‌فرض همه دستگیره‌های رویداد در مرحله Bubbling ثبت شده‌اند. از این رو در مثال فوق زمانی که روی ویدئو کلیک می‌کنید، رویداد کلیک از عنصر <video> به سمت بالا و تا عنصر <html> می‌رود. در این مسیر اتفاقات زیر رخ می‌دهند:

  • دستگیره رویداد متوجه ... video.onclick می‌شود و آن را اجرا می‌کند، بنابراین ابتدا ویدئو شروع به پخش می‌کند.
  • سپس دستگیره ... ideoBox.onclick را یافته و آن را اجرا می‌کند و از این رو ویدئو مخفی می‌شود.

اصلاح مشکل با ()stopPropagation

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

از این رو می‌توانیم مشکل فوق را با تغییر دادن تابع دستگیره دوم در بلوک کد قبلی به صورت زیر اصلاح کنیم:

1video.onclick = function(e) {
2  e.stopPropagation();
3  video.play();
4};

سعی کنید که نسخه‌ای از فایل این مثال را روی سیستم خود ایجاد کرده و اقدام به اصلاح این مشکل کنید. همچنین می‌توانید نسخه اصلاح شده را در ادامه مشاهده کنید:

1<!DOCTYPE html>
2<html>
3  <head>
4    <meta charset="utf-8">
5    <title>Show video box example</title>
6    <style>
7      div {
8        position: absolute;
9        top: 50%;
10        transform: translate(-50%,-50%);
11        width: 480px;
12        height: 380px;
13        border-radius: 10px;
14        background-color: #eee;
15        background-image: linear-gradient(to bottom, rgba(0,0,0,0), rgba(0,0,0,0.1));
16      }
17      .hidden {
18        left: -50%;
19      }
20      .showing {
21        left: 50%;
22      }
23      div video {
24        display: block;
25        width: 400px;
26        margin: 40px auto;
27      }
28    </style>
29  </head>
30  <body>
31    <button>Display video</button>
32
33    <div class="hidden">
34      <video>
35        <source src="rabbit320.mp4" type="video/mp4">
36        <source src="rabbit320.webm" type="video/webm">
37        <p>Your browser doesn't support HTML5 video. Here is a <a href="rabbit320.mp4">link to the video</a> instead.</p> 
38      </video>
39    </div>
40
41    <script>
42      var btn = document.querySelector('button');
43      var videoBox = document.querySelector('div');
44      var video = document.querySelector('video');
45      btn.onclick = function() {
46        videoBox.setAttribute('class','showing');
47      }
48      videoBox.onclick = function() {
49        videoBox.setAttribute('class','hidden');
50      };
51      video.onclick = function() {
52        video.play();
53      };
54      
55    </script>
56  </body>
57</html>

اگر مشتاق هستید که بدانید چرا باید با این دو مفهوم capturing و bubbling سر و کار داشته باشیم، باید بگوییم که در زمان‌های قدیم وقتی که مرورگرها قابلیت مطابقت کمتری داشتند، نت اسکیپ صرفاً از capturing استفاده می‌کرد و اینترنت اکسپلورر نیز تنها bubbling را مورد استفاده قرار می‌داد. زمانی که W3C تصمیم گرفت این رفتار را استاندارد کرده و به یک اجماع برسد در نهایت به این سیستم رسیدند که شامل هر دو گزینه است و در مرورگرهای مدرن آن‌ها را پیاده‌سازی کردند.

همان طور که پیش‌تر اشاره کردیم همه دستگیره‌های رویداد به صورت پیش‌فرض در مرحله bubbling ثبت شده‌اند و این وضعیت در اغلب موارد کاربرد بیشتری دارد. اگر واقعاً می‌خواهید یک رویداد را به جای bubbling در capturing ثبت کنید، می‌توانید این کار را از طریق ثبت دستگیره خود با استفاده از ()addEventListener و تعیین خصوصیت اختیاری سوم به صورت true انجام دهید.

انتقال رویداد

Bubbling به ما امکان بهره‌مندی از مفهومی به نام «انتقال رویداد» (event delegation) را نیز می‌دهد. این مفهوم بر مبنای این واقعیت عمل می‌کند که اگر بخواهیم کدی در هنگام کلیک شدن هر تعداد از عناصر فرزند اجرا شود، می‌توانیم شنونده رویداد را روی والد آن‌ها تعیین کنیم و رویدادهایی داشته باشیم که به صورت bubbling و زنجیری روی والدین آن‌ها اجرا شوند. در این حالت دیگر لازم نیست که شنونده رویداد را برای هر فرزند به صورت مجزا تعیین کنیم.

مثال خوبی از این وضعیت یک سری از آیتم‌های لیست است. اگر بخواهید هر یک از آن‌ها در هنگام کلیک شدن یک پیام کوچک را نمایش دهند، می‌توانید شنونده رویداد click را روی والد <ul> تنظیم کنید تا روی همه آیتم‌های لیست اجرا شود.

سخن پایانی

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

ضمناً لازم است بدانید که محیط‌های مختلفی که جاوا اسکریپت در آن استفاده می‌شود مدل‌های رویداد متفاوتی دارد و از API-های وب تا زمینه‌های دیگری مانند WebExtension-های مرورگر و Node.js یعنی جاوا اسکریپت در سمت سرور متفاوت هستند. ما انتظار نداریم که شما همه این زمینه‌ها را در حال حاضر بدانید؛ اما درک این موارد قطعاً به یادگیری بهتر مبانی رویدادها در مسیر پیش رو به سمت آموزش توسعه وب کمک شایان توجهی خواهد کرد.

برای مطالعه قسمت بعدی این مطلب روی لینک زیر کلیک کنید:

اگر این مطلب برای شما مفید بوده است، آموزش‌های زیر نیز به شما پیشنهاد می‌شوند:

==

بر اساس رای ۱ نفر
آیا این مطلب برای شما مفید بود؟
اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.
منابع:
developer.mozilla
نظر شما چیست؟

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *