نظرة معمّقة على أحداث JavaScript

preventDefault وstopPropagation: حالات استخدام كل طريقة، وما هي وظيفة كل طريقة بالضبط

Event.stopPropagation() وEvent.preventDefault()

غالبًا ما تكون معالجة أحداث JavaScript مباشرة. هذا صحيح بشكل خاص عند التعامل مع بنية HTML بسيطة (مسطحة نسبيًا). تصبح الأمور أكثر مشاركة قليلاً على الرغم من ذلك عندما تسير الأحداث (أو تنتشر) من خلال التسلسل الهرمي للعناصر. يحدث هذا عادةً عندما يستعين المطوّرون بـ stopPropagation() و/أو preventDefault() لحل المشاكل التي يواجهونها. إذا كنت قد فكرت في نفسك "سأجرِّب preventDefault() فقط وإذا لم ينجح ذلك، سأحاول stopPropagation() وإذا لم ينجح ذلك، فسأجرب الأمرين،" فهذه المقالة تناسبك. سأشرح بالضبط ما تفعله كل طريقة، ومتى تستخدم أي طريقة، وأقدم لك مجموعة متنوعة من الأمثلة العملية لتستكشفها. هدفي هو إنهاء الالتباس مرة واحدة وإلى الأبد.

وقبل أن نتعمّق في التفاصيل، من المهم التطرق بإيجاز إلى نوعَي معالجة الأحداث الممكن في JavaScript (في جميع المتصفّحات الحديثة، أي أنّ Internet Explorer قبل الإصدار 9 لا يتيح تسجيل الأحداث على الإطلاق).

أنماط الأحداث (الالتقاط والفقاعات)

وتتيح جميع المتصفّحات الحديثة تسجيل الأحداث، ولكن نادرًا ما يستخدمها المطوّرون. من المثير للاهتمام أنّ Netscape كانت شكل الأحداث الوحيد الذي دعمه في الأصل. أما المنافس الأكبر لـ Netscape، وهو Microsoft Internet Explorer، فلم يدعم التقاط الأحداث على الإطلاق، بل سمح فقط باستخدام أسلوب آخر من الأحداث يُسمى فقاعات الأحداث. عندما تم تشكيل W3C، وجدت فائدة في كلا أسلوبَي الأحداث وأعلنت أنّ المتصفحات يجب أن تتوافق مع كلا الأسلوبين، عن طريق مَعلمة ثالثة إلى طريقة addEventListener. في الأصل، كانت هذه المعلَمة مجرّد قيمة منطقية، ولكن جميع المتصفحات الحديثة تتوافق مع الكائن options كمَعلمة ثالثة، ويمكنك استخدامها لتحديدها (من بين أمور أخرى) إذا كنت تريد استخدام تسجيل الأحداث أم لا:

someElement.addEventListener('click', myClickHandler, { capture: true | false });

يُرجى العِلم أنّ الكائن options اختياري، مثل السمة capture الخاصة به. في حال حذف أي منهما، ستكون القيمة التلقائية للسمة capture هي false، ما يعني أنّه سيتم استخدام فقاعات الأحداث.

التقاط الأحداث

ماذا يعني إذا كان معالج الحدث "يستمع في مرحلة الالتقاط"؟ لفهم هذا، نحتاج إلى معرفة كيف تنشأ الأحداث وكيف تسير. ينطبق ما يلي على جميع الأحداث، حتى لو لم تستفيد، بصفتك المطوِّر، من الاستفادة من هذا الأمر أو تهتم به أو تفكر فيه.

وتبدأ جميع الأحداث من النافذة وتمرّ بمرحلة التقاط الصورة أولاً. وهذا يعني أنّه عند إرسال الحدث، يبدأ النافذة وينتقل "نزولاً" نحو العنصر المستهدَف أولاً. ويحدث ذلك حتى إذا كنت تستمع إلى المحتوى فقط في مرحلة الفقاعات. ضع في الاعتبار المثال التالي للترميز وJavaScript:

<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 was click" ثم انتهينا من ذلك، أليس كذلك؟ إجابة غير صحيحة. لم ننتهي على الإطلاق. ستستمر العملية، لكنها الآن تتغير إلى مرحلة الفقاعات.

فقاعات الأحداث

سيطلب منك المتصفّح ما يلي:

"هل يتم الاستماع إلى حدث ناتج عن النقر على #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 في العرض التوضيحي المباشر وراقب النتائج التي يتم الحصول عليها من وحدة التحكّم.

واحدة أخرى، فقط للمرح. ماذا يحدث إذا استدعينا stopPropagation() في المرحلة المستهدفة لـ #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"

تجدر الإشارة إلى أنّ معالج الأحداث في #C الذي نسجّل فيه "click on #C في مرحلة الالتقاط" لا يزال يُنفذ، بينما لا يتم تنفيذ معالج الحدث "click on #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()، أضِف كلمة "إجراء" في رأسك. فكر في "منع الإجراء الافتراضي".

وما هو الإجراء التلقائي الذي قد تطرحه؟ لسوء الحظ، الإجابة على ذلك ليست واضحة تمامًا لأنها تعتمد بشكل كبير على تركيبة العنصر + الحدث المعنية. ولجعل الأمور أكثر إرباكًا، في بعض الأحيان لا يوجد إجراء افتراضي على الإطلاق!

لنبدأ بمثال بسيط جدًا لفهمه. ماذا تتوقع أن يحدث عند النقر فوق رابط في صفحة ويب؟ وبالطبع، أنت تتوقع أن ينتقل المتصفح إلى عنوان 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> + حدث "إرسال": سيؤدي preventDefault() لهذه المجموعة إلى منع إرسال النموذج. ويكون هذا مفيدًا إذا كنت تريد إجراء عملية التحقق وفي حال تعذُّر إتمام العملية، يمكنك استدعاء الإجراء preventDefault في إيقاف إرسال النموذج.

  • عنصر <a> + حدث "النقر": يمنع preventDefault() لهذه المجموعة المتصفّح من الانتقال إلى عنوان URL المحدّد في السمة href الخاصة بالعنصر <a>.

  • document + حدث "عجلة الماوس": يمنع preventDefault() لهذه المجموعة تمرير الصفحات باستخدام عجلة الماوس (يظلّ التمرير باستخدام لوحة المفاتيح يعمل).
    ↜ يتطلب ذلك الاتصال بـ addEventListener() باستخدام { passive: false }.

  • document + حدث "keydown": preventDefault() لهذه التركيبة قاتلة. فهو يجعل الصفحة عديمة الفائدة إلى حد كبير، مما يمنع التمرير عبر لوحة المفاتيح، وعلامات التبويب، وتمييز لوحة المفاتيح.

  • document + حدث "الماوس": سيؤدي إدخال preventDefault() لهذه المجموعة إلى منع تمييز النص باستخدام الماوس وأي إجراء "تلقائي" آخر قد يستدعيه عندما ينقر المستخدم على الماوس.

  • عنصر <input> + حدث "الضغط على المفاتيح": preventDefault() لهذه التركيبة، سيؤدي ذلك إلى منع الأحرف التي كتبها المستخدم من الوصول إلى عنصر الإدخال (ولكن لا تنفّذ هذا الإجراء، فنادرًا ما يكون هناك سبب وجيه لذلك، إن وُجد).

  • document + حدث "قائمة السياقات": يؤدي preventDefault() لهذه المجموعة إلى منع ظهور قائمة سياقات المتصفِّح الأصلي عند نقر المستخدم بزر الماوس الأيمن أو ضغطه مع الاستمرار (أو أي طريقة أخرى قد تظهر بها قائمة السياقات).

يُرجى العِلم بأنّ هذه القائمة ليست شاملة بأي حال من الأحوال، ولكنّنا نأمل أن تقدّم لك فكرة جيدة عن كيفية استخدام preventDefault().

أو نكتة عملية طريفة؟

ماذا يحدث في حال استخدام stopPropagation() و preventDefault() في مرحلة الالتقاط بدءًا من المستند؟ يليه الضحك! سيؤدي مقتطف الرمز التالي إلى عرض أي صفحة ويب عديمة الفائدة تمامًا:

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()، والتي ستتذكرها تمنع الإجراء التلقائي. ولذلك، يتم منع أي إجراءات تلقائية (مثل تمرير عجلة الماوس، أو تمرير لوحة المفاتيح أو التمييز أو الجدولة، والنقر على الرابط، وعرض قائمة السياق، وما إلى ذلك)، ما يؤدي إلى ترك الصفحة في حالة عديمة الفائدة إلى حدٍ ما.

عروض توضيحية مباشرة

لاستكشاف جميع الأمثلة من هذه المقالة مرة أخرى في مكان واحد، تحقق من العرض التوضيحي المضمَّن أدناه.

شكر وتقدير

صورة رئيسية من تصوير توم ويلسون على موقع Unسبلاش