เจาะลึกเหตุการณ์ JavaScript

preventDefault และ stopPropagation: เมื่อใดควรใช้วิธีใด และแต่ละวิธีทำอะไรได้บ้าง

Event.stopPropagation() และ Event.preventDefault()

การจัดการเหตุการณ์ JavaScript มักจะตรงไปตรงมา โดยเฉพาะอย่างยิ่งเมื่อต้องจัดการกับโครงสร้าง HTML ที่ เรียบง่าย (ค่อนข้างแบน) แต่จะมีความซับซ้อนมากขึ้นเมื่อเหตุการณ์ เดินทาง (หรือแพร่กระจาย) ผ่านลำดับชั้นขององค์ประกอบ โดยปกติแล้ว นักพัฒนาแอปจะติดต่อstopPropagation()และ/หรือpreventDefault()เพื่อแก้ปัญหาที่พบ หากคุณเคยคิดว่า "ฉันจะลองpreventDefault()ก่อน ถ้าไม่ได้ผลก็ลองstopPropagation() ถ้าไม่ได้ผลอีกก็ลองทั้งสองอย่าง" บทความนี้เหมาะสำหรับคุณ เราจะอธิบายการทำงานของแต่ละวิธีอย่างละเอียด เวลาที่ควรใช้วิธีใด และยกตัวอย่างการทำงานที่หลากหลายให้คุณได้ลองใช้ เป้าหมายของฉันคือการขจัดความสับสนของคุณให้หมดไป

อย่างไรก็ตาม ก่อนที่จะเจาะลึกในรายละเอียด คุณควรทราบโดยสังเขปเกี่ยวกับการจัดการเหตุการณ์ 2 ประเภท ที่ทำได้ใน JavaScript (ในเบราว์เซอร์สมัยใหม่ทั้งหมด นั่นคือ Internet Explorer ก่อนเวอร์ชัน 9 ไม่ รองรับการดักจับเหตุการณ์เลย)

รูปแบบการเกิดเหตุการณ์ (การจับภาพและการฟอง)

เบราว์เซอร์สมัยใหม่ทั้งหมดรองรับการดักจับเหตุการณ์ แต่มีนักพัฒนาแอปน้อยมากที่ใช้ฟีเจอร์นี้ ที่น่าสนใจคือ นี่เป็นรูปแบบการจัดการเหตุการณ์เพียงรูปแบบเดียวที่ Netscape รองรับในตอนแรก คู่แข่งที่สำคัญที่สุดของ Netscape อย่าง Microsoft Internet Explorer ไม่รองรับการดักจับเหตุการณ์เลย แต่รองรับเฉพาะรูปแบบการเกิดเหตุการณ์อีกรูปแบบหนึ่งที่เรียกว่าการฟองเหตุการณ์ เมื่อมีการก่อตั้ง W3C ก็พบว่าทั้ง 2 รูปแบบของเหตุการณ์มีข้อดี และประกาศว่าเบราว์เซอร์ควรรองรับทั้ง 2 รูปแบบผ่านพารามิเตอร์ที่ 3 ของเมธอด addEventListener เดิมทีพารามิเตอร์นั้นเป็นเพียงบูลีน แต่เบราว์เซอร์สมัยใหม่ทั้งหมด รองรับออบเจ็กต์ options เป็นพารามิเตอร์ที่ 3 ซึ่งคุณใช้เพื่อระบุได้ (นอกเหนือจากสิ่งอื่นๆ) ว่าต้องการใช้การดักจับเหตุการณ์หรือไม่

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 clicked" แล้วเราก็เสร็จสิ้นใช่ไหม ผิด เรายังไม่เสร็จเลย กระบวนการจะดำเนินต่อไป แต่จะเปลี่ยนเป็นระยะการแสดงเนื้อหาที่เกี่ยวข้อง

การฟองเหตุการณ์

เบราว์เซอร์จะถามว่า

"มีอะไรที่รอรับเหตุการณ์คลิกใน #C ในระยะฟองไหม" โปรดตั้งใจฟังตรงนี้ คุณสามารถฟังการคลิก (หรือเหตุการณ์ประเภทใดก็ได้) ในทั้งการจับและ ระยะฟองได้ และหากคุณเชื่อมต่อตัวแฮนเดิลเหตุการณ์ในทั้ง 2 เฟส (เช่น โดยการเรียกใช้ .addEventListener() 2 ครั้ง ครั้งหนึ่งด้วย capture = true และอีกครั้งด้วย capture = false) ก็ใช่ ตัวแฮนเดิลเหตุการณ์ทั้ง 2 จะทํางานสําหรับองค์ประกอบเดียวกันอย่างแน่นอน แต่ก็ควรทราบด้วยว่า เหตุการณ์จะทริกเกอร์ในระยะต่างๆ (1 รายการในระยะการจับภาพและ 1 รายการในระยะการฟอง)

จากนั้น เหตุการณ์จะเผยแพร่ (โดยทั่วไปเรียกว่า "ฟอง" เนื่องจากดูเหมือนว่าเหตุการณ์ จะเดินทาง "ขึ้น" ต้นไม้ DOM) ไปยังองค์ประกอบหลัก #B และเบราว์เซอร์จะถามว่า "มี อะไรที่รอฟังเหตุการณ์คลิกใน #B ในระยะฟองไหม" ในตัวอย่างของเราไม่มีอะไรเลย ดังนั้น จึงไม่มีตัวแฮนเดิลใดทำงาน

จากนั้น เหตุการณ์จะส่งต่อไปยัง #A และเบราว์เซอร์จะถามว่า "มีอะไรที่รอรับเหตุการณ์คลิก ใน #A ในระยะการส่งต่อไหม"

จากนั้น เหตุการณ์จะส่งต่อไปยัง <body>: "มีอะไรที่รอรับฟังเหตุการณ์คลิกใน<body> องค์ประกอบในระยะการส่งต่อหรือไม่"

ต่อไปคือองค์ประกอบ <html>: "มีอะไรที่รอฟังเหตุการณ์คลิกในองค์ประกอบ <html> ใน ระยะฟองไหม

จากนั้น document: "มีอะไรที่รอรับเหตุการณ์คลิกใน document ในระยะฟองสบู่อยู่ไหม "

สุดท้าย window: "มีอะไรที่รอรับฟังเหตุการณ์คลิกในหน้าต่างในระยะฟองไหม"

ในที่สุด การเดินทางครั้งนี้ยาวนานมาก และตอนนี้กิจกรรมของเราอาจเหนื่อยล้าไปแล้ว แต่ไม่ว่าคุณจะเชื่อหรือไม่ การเดินทางนี้เป็นสิ่งที่ทุกกิจกรรมต้องเผชิญ โดยส่วนใหญ่แล้วเราจะไม่สังเกตเห็นสิ่งนี้เนื่องจาก โดยปกติแล้วนักพัฒนาซอฟต์แวร์จะสนใจเฉพาะเฟสเหตุการณ์ใดเฟสหนึ่งเท่านั้น (และมักจะเป็น เฟสฟองสบู่)

คุณควรใช้เวลาสักครู่เพื่อลองเล่นกับการดักจับเหตุการณ์และการฟองเหตุการณ์ รวมถึงบันทึก โน้ตบางอย่างไปยังคอนโซลเมื่อตัวแฮนเดิลเริ่มทำงาน การดูเส้นทางที่เหตุการณ์ ใช้เป็นข้อมูลเชิงลึกที่มีประโยชน์มาก ตัวอย่างด้านล่างนี้จะรับฟังทุกองค์ประกอบในทั้ง 2 เฟส

<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"

event.stopPropagation()

เมื่อเข้าใจว่าเหตุการณ์เกิดขึ้นที่ใดและเดินทางอย่างไร (เช่น แพร่กระจาย) ผ่าน DOM ทั้งในระยะการจับภาพและระยะการฟอง เราก็สามารถหันมาสนใจevent.stopPropagation()ได้แล้ว

คุณเรียกใช้เมธอด stopPropagation() ได้ในเหตุการณ์ DOM ดั้งเดิม (ส่วนใหญ่) ที่บอกว่า "ส่วนใหญ่" ก็เพราะมีบางรายการที่การเรียกใช้เมธอดนี้จะไม่มีผลใดๆ (เนื่องจากเหตุการณ์ไม่ได้เผยแพร่ตั้งแต่แรก) กิจกรรมต่างๆ เช่น focus, blur, load, scroll และอื่นๆ อีกเล็กน้อยจะอยู่ในหมวดหมู่นี้ คุณโทรหา stopPropagation() ได้ แต่จะไม่มีอะไรเกิดขึ้นเนื่องจากเหตุการณ์เหล่านี้ ไม่ได้แพร่กระจาย

แต่ stopPropagation ทำอะไรได้บ้าง

โดยจะทำงานตามที่ระบุไว้ เมื่อเรียกใช้ เหตุการณ์จะหยุด ส่งต่อไปยังองค์ประกอบใดๆ ที่จะส่งต่อไปยังองค์ประกอบนั้น ซึ่งเป็นจริงทั้ง 2 ทิศทาง (การจับภาพและการส่งต่อ) ดังนั้นหากคุณเรียกใช้ 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"

แล้วจะหยุดการแพร่กระจายที่ #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"

อีกอันเพื่อความสนุก จะเกิดอะไรขึ้นหากเราเรียกใช้ 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 ซึ่งเราบันทึก "คลิกที่ #C ในระยะการจับภาพ" ยังคง ทํางาน แต่เครื่องจัดการเหตุการณ์ซึ่งเราบันทึก "คลิกที่ #C ในระยะการฟอง" จะไม่ทํางาน ซึ่งควรจะ สมเหตุสมผล เราเรียก stopPropagation() จากอดีต ดังนั้นจุดนี้คือจุดที่การแพร่กระจายของเหตุการณ์จะสิ้นสุดลง

ฉันขอแนะนำให้คุณลองเล่นในเดโมแบบสดเหล่านี้ ลองคลิกเฉพาะองค์ประกอบ #A หรือ เฉพาะองค์ประกอบ body ลองคาดเดาว่าจะเกิดอะไรขึ้น แล้วดูว่าคุณคาดเดาถูกต้องหรือไม่ ณ จุดนี้ คุณควรจะคาดการณ์ได้อย่างแม่นยำ

event.stopImmediatePropagation()

วิธีที่แปลกประหลาดและไม่ค่อยได้ใช้คืออะไร ซึ่งคล้ายกับ stopPropagation แต่แทนที่จะ หยุดไม่ให้เหตุการณ์เดินทางไปยังองค์ประกอบลูก (การจับภาพ) หรือองค์ประกอบแม่ (การฟอง) วิธีนี้จะ ใช้ได้ก็ต่อเมื่อคุณมีตัวแฮนเดิลเหตุการณ์มากกว่า 1 รายการที่เชื่อมต่อกับองค์ประกอบเดียว เนื่องจาก addEventListener() รองรับรูปแบบการส่งเหตุการณ์แบบมัลติคาสต์ คุณจึงเชื่อมต่อตัวแฮนเดิลเหตุการณ์กับองค์ประกอบเดียวได้มากกว่า 1 ครั้ง เมื่อเกิดเหตุการณ์นี้ (ในเบราว์เซอร์ส่วนใหญ่) ระบบจะเรียกใช้ตัวแฮนเดิลเหตุการณ์ตามลำดับที่เชื่อมต่อไว้ การเรียกใช้ 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!"

โปรดทราบว่าตัวแฮนเดิลเหตุการณ์ที่ 3 จะไม่ทำงานเนื่องจากตัวแฮนเดิลเหตุการณ์ที่ 2 เรียกใช้ e.stopImmediatePropagation() แต่หากเราเรียกใช้ e.stopPropagation() แทน ตัวแฮนเดิลที่สาม ก็จะยังคงทำงาน

event.preventDefault()

หาก stopPropagation() ป้องกันไม่ให้เหตุการณ์ "ลง" (จับ) หรือ "ขึ้น" (ฟอง) แล้ว preventDefault() จะทำอะไร ดูเหมือนว่าฟีเจอร์นี้จะทำงานคล้ายกัน Does it?

ไม่ครับ แม้ว่าทั้ง 2 อย่างนี้มักจะทำให้เกิดความสับสน แต่จริงๆ แล้วทั้ง 2 อย่างนี้ไม่ได้มีความเกี่ยวข้องกันมากนัก เมื่อเห็น preventDefault() ให้เพิ่มคำว่า "การดำเนินการ" ในใจ คิดว่า "ป้องกันการดำเนินการเริ่มต้น"

และคุณอาจถามว่าการดำเนินการเริ่มต้นคืออะไร ขออภัย คำตอบสำหรับคำถามนั้นไม่ชัดเจนนัก เนื่องจากขึ้นอยู่กับการผสมผสานขององค์ประกอบและเหตุการณ์ที่เป็นปัญหา และที่ทำให้สับสนยิ่งกว่าเดิมคือ บางครั้งก็ไม่มีการดำเนินการเริ่มต้นเลย

มาเริ่มด้วยตัวอย่างที่ง่ายมากเพื่อให้เข้าใจกัน คุณคาดหวังว่าจะเกิดอะไรขึ้นเมื่อคลิกลิงก์ในหน้าเว็บ แน่นอนว่าคุณคงคาดหวังให้เบราว์เซอร์ไปยัง URL ที่ลิงก์นั้นระบุ ในกรณีนี้ องค์ประกอบคือแท็ก Anchor และเหตุการณ์คือเหตุการณ์คลิก โดยการกดปุ่มร่วมกัน (<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 จะนำไปสู่การเรียกดูwww.theavettbrothers.com แต่ในกรณีนี้ เราได้เชื่อมต่อตัวแฮนเดิลเหตุการณ์คลิกกับองค์ประกอบ <a> และระบุว่าควรป้องกันการดำเนินการเริ่มต้น ดังนั้นเมื่อผู้ใช้คลิกลิงก์นี้ ระบบจะไม่นำผู้ใช้ไปที่ใดเลย แต่จะบันทึกข้อความ "หรือเราควร เปิดเพลงของศิลปินที่นี่แทนดีไหม" ในคอนโซล

ค่าผสมขององค์ประกอบ/เหตุการณ์อื่นๆ ที่ช่วยให้คุณป้องกันการดำเนินการเริ่มต้นมีอะไรบ้าง ฉันไม่สามารถ แสดงรายการทั้งหมดได้ และบางครั้งคุณก็ต้องทดลองเพื่อดู โดยสรุปแล้ว ข้อกำหนดบางส่วนมีดังนี้

  • องค์ประกอบ <form> + เหตุการณ์ "submit": preventDefault() สำหรับชุดค่าผสมนี้จะป้องกันไม่ให้แบบฟอร์ม ส่ง วิธีนี้มีประโยชน์หากคุณต้องการดำเนินการตรวจสอบ และหากมีข้อผิดพลาด คุณจะเรียกใช้ preventDefault แบบมีเงื่อนไขเพื่อหยุดการส่งแบบฟอร์มได้

  • องค์ประกอบ <a> + เหตุการณ์ "คลิก": preventDefault() สำหรับชุดค่าผสมนี้จะป้องกันไม่ให้เบราว์เซอร์ ไปยัง URL ที่ระบุในแอตทริบิวต์ href ขององค์ประกอบ <a>

  • document + เหตุการณ์ "mousewheel": preventDefault() สำหรับชุดค่าผสมนี้จะป้องกันการเลื่อนหน้าเว็บ ด้วยล้อเลื่อนของเมาส์ (แต่การเลื่อนด้วยแป้นพิมพ์จะยังคงใช้งานได้)
    ↜ ต้องเรียกใช้ addEventListener() ด้วย { passive: false }

  • document + เหตุการณ์ "keydown": preventDefault() สำหรับชุดค่าผสมนี้เป็นอันตราย ซึ่งทำให้หน้าเว็บแทบจะใช้ไม่ได้เลย โดยจะป้องกันการเลื่อนด้วยแป้นพิมพ์ การกดแป้น Tab และการไฮไลต์ด้วยแป้นพิมพ์

  • document + เหตุการณ์ "mousedown": preventDefault() สำหรับการผสมผสานนี้จะป้องกันการไฮไลต์ข้อความด้วยเมาส์และการดำเนินการ "ค่าเริ่มต้น" อื่นๆ ที่ผู้ใช้จะเรียกใช้ด้วยการกดเมาส์ลง

  • องค์ประกอบ <input> + เหตุการณ์ "keypress": preventDefault() สำหรับการผสมผสานนี้จะป้องกันไม่ให้ อักขระที่ผู้ใช้พิมพ์ไปถึงองค์ประกอบอินพุต (แต่ไม่ต้องทำเช่นนี้ เนื่องจาก แทบจะไม่มีเหตุผลที่ถูกต้องสำหรับการทำเช่นนี้)

  • document + เหตุการณ์ "contextmenu": 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() ซึ่งคุณคงจำได้ว่าเป็นการป้องกันการดำเนินการเริ่มต้น ดังนั้นการดำเนินการเริ่มต้นใดๆ (เช่น การเลื่อนด้วยล้อเมาส์ การเลื่อนด้วยแป้นพิมพ์ การไฮไลต์หรือการกด Tab การคลิกลิงก์ การแสดงเมนูตามบริบท ฯลฯ) จะถูกป้องกันทั้งหมด ทำให้หน้าเว็บอยู่ในสถานะที่ค่อนข้างไร้ประโยชน์

คำขอบคุณ

รูปภาพหลักโดย Tom Wilson ใน Unsplash