preventDefault
และ stopPropagation
: กรณีที่ควรใช้แต่ละวิธีและแต่ละวิธีทํางานอย่างไร
Event.stopPropagation() และ Event.preventDefault()
การจัดการเหตุการณ์ JavaScript มักจะตรงไปตรงมา โดยเฉพาะอย่างยิ่งเมื่อจัดการกับโครงสร้าง HTML ที่เรียบง่าย (ค่อนข้างราบเรียบ) อย่างไรก็ตาม สถานการณ์จะซับซ้อนขึ้นเมื่อเหตุการณ์เดินทางไป (หรือนำไปใช้) ผ่านลําดับชั้นขององค์ประกอบ ซึ่งมักเกิดขึ้นเมื่อนักพัฒนาแอปติดต่อขอ stopPropagation()
และ/หรือ preventDefault()
เพื่อแก้ปัญหาที่พบ หากคุณเคยคิดว่า "ฉันจะลองใช้ preventDefault()
แล้วหากไม่ได้ผล ฉันจะลองใช้ stopPropagation()
แล้วหากไม่ได้ผล ฉันจะลองใช้ทั้ง 2 อย่าง" บทความนี้เหมาะกับคุณ เราจะอธิบายว่าแต่ละวิธีทํางานอย่างไร กรณีใดควรใช้วิธีใด และแสดงตัวอย่างการใช้งานที่หลากหลายให้คุณได้ดู เป้าหมายของฉันคือขจัดความสับสนของคุณออกไปให้ได้เพียงครั้งเดียว
แต่ก่อนที่จะเจาะลึกลงไป เราขออธิบายสั้นๆ เกี่ยวกับการจัดการเหตุการณ์ 2 ประเภทที่เป็นไปได้ใน JavaScript (ในเบราว์เซอร์สมัยใหม่ทั้งหมด เนื่องจาก Internet Explorer ก่อนเวอร์ชัน 9 ไม่รองรับการจับเหตุการณ์เลย)
รูปแบบเหตุการณ์ (การบันทึกและการรวม)
เบราว์เซอร์สมัยใหม่ทั้งหมดรองรับการบันทึกเหตุการณ์ แต่นักพัฒนาแอปไม่ค่อยได้ใช้
ที่น่าสนใจคือรูปแบบนี้คือรูปแบบเดียวที่ Netscape รองรับในตอนแรก Microsoft Internet Explorer คู่แข่งรายใหญ่ที่สุดของ Netscape ไม่รองรับการบันทึกเหตุการณ์เลย แต่รองรับเฉพาะเหตุการณ์อีกรูปแบบที่เรียกว่าการฟองสบู่ของกิจกรรม เมื่อก่อตั้ง 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" แล้วเราก็เสร็จแล้ว ถูกต้องไหม
ไม่ถูกต้อง เรายังไม่จบ กระบวนการจะยังคงดำเนินต่อไป แต่ตอนนี้จะเปลี่ยนเป็นระยะการทดสอบ
การทําให้เหตุการณ์ปรากฏ
เบราว์เซอร์จะถามคำถามต่อไปนี้
"มีสิ่งใดเฝ้าติดตามเหตุการณ์การคลิกใน #C
ในระยะการบับเบิลไหม" โปรดตั้งตารอที่นี่
คุณสามารถเฝ้าติดตามการคลิก (หรือเหตุการณ์ประเภทใดก็ได้) ทั้งในระยะการบันทึกและระยะการบับเบิล และหากคุณได้ต่อสายให้กับตัวแฮนเดิลเหตุการณ์ทั้ง 2 ช่วง (เช่น โดยการเรียกใช้ .addEventListener()
2 ครั้ง โดย 1 ครั้งใช้ capture = true
และอีก 1 ครั้งใช้ capture = false
) ตัวแฮนเดิลเหตุการณ์ทั้ง 2 รายการจะทํางานสําหรับองค์ประกอบเดียวกันอย่างแน่นอน แต่สิ่งสำคัญที่ต้องทราบด้วยว่า
กล้องจะเริ่มทำงานเป็นระยะที่ต่างกัน (ระยะหนึ่งในระยะจับภาพและอีกระยะหนึ่งในช่วงฟองสบู่)
ถัดไป เหตุการณ์จะแพร่กระจาย (หรือที่เรียกกันโดยทั่วไปว่า "ทําให้เกิดเหตุการณ์ต่อเนื่อง" เนื่องจากดูเหมือนว่าเหตุการณ์จะ "ขึ้น" ไปตามลําดับชั้น DOM) ไปยังองค์ประกอบหลัก #B
และเบราว์เซอร์จะถามว่า "มีสิ่งใดรับฟังเหตุการณ์การคลิกใน #B
ในระยะการทําให้เกิดเหตุการณ์ต่อเนื่องไหม" ในตัวอย่างของเรา ไม่มีเงื่อนไขใดๆ อยู่เลย ดังนั้นจึงไม่มีตัวแฮนเดิลใดทำงาน
จากนั้นเหตุการณ์จะส่งไปยัง #A
และเบราว์เซอร์จะถามว่า "มีสิ่งใดกำลังรอฟังเหตุการณ์การคลิกใน #A
ในระยะการบับเบิลไหม"
ถัดไป เหตุการณ์จะแสดงเป็น <body>
ว่า "มีการคอยฟังกิจกรรมการคลิกในองค์ประกอบ <body>
ในระยะบับเบิลไหม"
ถัดไปคือองค์ประกอบ <html>
: "มีสิ่งใดกำลังรอเหตุการณ์คลิกในองค์ประกอบ <html>
ในระยะการทํางานแบบบับเบิลหรือไม่
ถัดไปคือ document
: "มีสิ่งใดกำลังรอเหตุการณ์คลิกใน document
ในระยะการทํางานแบบ Bubbling อยู่ไหม"
สุดท้ายคือ window
: "มีสิ่งใดกำลังรอเหตุการณ์คลิกในหน้าต่างในระยะการทํางานแบบ Bubbling ไหม"
ในที่สุด นั่นเป็นการเดินทางที่ยาวนาน กิจกรรมของเราอาจจะเหนื่อยมากในตอนนี้ แต่เชื่อหรือไม่ว่านี่คือเส้นทางที่ทุกเหตุการณ์ต้องผ่านมา ส่วนใหญ่แล้ว เหตุการณ์นี้จะไม่ได้รับการสังเกตเนื่องจากนักพัฒนาแอปมักสนใจเฉพาะระยะของเหตุการณ์ใดเหตุการณ์หนึ่ง (และมักจะเป็นระยะการเพิ่มขึ้น)
คุณควรลองเล่นกับการบันทึกเหตุการณ์และการบับเบิ้ลเหตุการณ์ รวมถึงบันทึกโน้ตบางอย่างลงในคอนโซลเมื่อตัวแฮนเดิลเริ่มทำงาน การดูเส้นทางที่เหตุการณ์เกิดขึ้นจะมีประโยชน์อย่างยิ่ง ต่อไปนี้เป็นตัวอย่างที่คอยฟังองค์ประกอบทุกรายการในทั้ง 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"
คุณสามารถลองใช้ฟีเจอร์นี้แบบอินเทอร์แอกทีฟได้ในวิดีโอสาธิตเวอร์ชันที่ใช้จริงด้านล่าง คลิกองค์ประกอบ #C
และสังเกตเอาต์พุตของคอนโซล
event.stopPropagation()
เมื่อเข้าใจแหล่งที่มาของเหตุการณ์และวิธีที่เหตุการณ์เดินทาง (นั่นคือ แพร่กระจาย) ผ่าน DOM ทั้งในระยะการบันทึกและระยะการทําให้เกิดเหตุการณ์แล้ว ตอนนี้เราก็สามารถหันไปสนใจevent.stopPropagation()
เรียกใช้เมธอด stopPropagation()
ได้ในเหตุการณ์ DOM เดิม (ส่วนใหญ่) เราใช้คำว่า "ส่วนใหญ่" เนื่องจากมีบางรายการที่การเรียกใช้เมธอดนี้จะไม่ทําอะไรเลย (เนื่องจากเหตุการณ์ไม่แผ่กระจายตั้งแต่แรก) เหตุการณ์ เช่น focus
, blur
, load
, scroll
และอีก 2-3 รายการจัดอยู่ในหมวดหมู่นี้ คุณสามารถเรียกใช้ 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"
โดยคุณเล่นเพลงแบบอินเทอร์แอกทีฟได้ในเดโมแบบสดด้านล่าง คลิกองค์ประกอบ #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
ซึ่งเราบันทึก "คลิกที่ #C ในเฟสการบันทึก" จะยังคงทำงานอยู่ แต่เครื่องจัดการเหตุการณ์ที่เราบันทึก "คลิกที่ #C ในเฟสการทํางานร่วมกัน" จะไม่ทํางาน ซึ่งก็น่าจะสมเหตุสมผล เราเรียก stopPropagation()
จากรายการแรก ดังนั้นจุดที่การนำไปใช้งานของเหตุการณ์จะหยุดลงคือจุดนั้น
โดยคุณเล่นเพลงแบบอินเทอร์แอกทีฟได้ในเดโมแบบสดด้านล่าง คลิกองค์ประกอบ #C
ในการสาธิตแบบสดและสังเกตผลลัพธ์ของคอนโซล
ในการสาธิตแบบสดเหล่านี้ เราขอแนะนำให้คุณลองเล่นดู ลองคลิกเฉพาะองค์ประกอบ #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()
แทน ตัวแฮนเดิลที่ 3 จะยังคงทำงานอยู่
event.preventDefault()
หาก stopPropagation()
ป้องกันไม่ให้เหตุการณ์ "เลื่อนลง" (การบันทึก) หรือ "เลื่อนขึ้น" (การบับเบิล) preventDefault()
จะทําอย่างไร ดูเหมือนว่าจะใช้เพื่อวัตถุประสงค์ที่คล้ายกัน ใช่หรือไม่
ไม่ครับ แม้ว่าคนมักจะสับสนระหว่าง 2 อย่างนี้ แต่จริงๆ แล้ว 2 อย่างนี้ไม่ได้เกี่ยวข้องกันมากนัก
เมื่อเห็น preventDefault()
ให้เพิ่มคำว่า "การดำเนินการ" ในใจ คิดถึง "การป้องกันการดำเนินการเริ่มต้น"
การดำเนินการเริ่มต้นที่คุณอาจสอบถามคืออะไร น่าเสียดายที่คำตอบนั้นยังไม่ชัดเจน เนื่องจากขึ้นอยู่กับชุดค่าผสมขององค์ประกอบและเหตุการณ์ที่เป็นปัญหา และยิ่งไปกว่านั้น บางครั้งก็ไม่มีการดำเนินการเริ่มต้นเลย
เรามาเริ่มด้วยตัวอย่างง่ายๆ ที่จะทำความเข้าใจ คุณคาดหวังให้สิ่งใดเกิดขึ้นเมื่อคลิกลิงก์ในหน้าเว็บ แน่นอนว่าคุณคาดหวังว่าเบราว์เซอร์จะไปยัง URL ที่ระบุโดยลิงก์นั้น
ในกรณีนี้ องค์ประกอบคือแท็กแอตทริบิวต์ "a" และเหตุการณ์คือเหตุการณ์การคลิก ชุดค่าผสมดังกล่าว (<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 แล้วดูเอาต์พุตคอนโซล (และข้อเท็จจริงที่ว่าระบบไม่ได้เปลี่ยนเส้นทางคุณไปยังเว็บไซต์ของ The Avett Brothers)
โดยปกติแล้ว การคลิกลิงก์ที่มีป้ายกำกับว่า 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
ดังนั้นในสnippet นี้ เราจะหยุดเหตุการณ์ click
, keydown
, mousedown
, contextmenu
และ mousewheel
ทั้งหมดไม่ให้เข้าถึงองค์ประกอบที่อาจรอรับเหตุการณ์เหล่านั้น นอกจากนี้เรายังเรียกใช้ stopImmediatePropagation
เพื่อให้แฮนเดิลที่ต่อสายไปยังเอกสารหลังจากหน้านี้ถูกขัดขวางเช่นกัน
โปรดทราบว่า stopPropagation()
และ stopImmediatePropagation()
ไม่ใช่ (อย่างน้อยก็ไม่ใช่ส่วนใหญ่) สาเหตุที่ทำให้หน้าเว็บใช้งานไม่ได้ เพื่อป้องกันไม่ให้เหตุการณ์ไปถึงปลายทางเหล่านั้น
แต่เราจะเรียก preventDefault()
ด้วย ซึ่งคุณอาจจำได้ว่าจะหยุดการดำเนินการเริ่มต้น ดังนั้น ระบบจะป้องกันการดำเนินการเริ่มต้นทั้งหมด (เช่น การเลื่อนด้วยปุ่มลูกล้อของเมาส์ การเลื่อนด้วยแป้นพิมพ์หรือการไฮไลต์หรือการกด Tab การคลิกลิงก์ การแสดงเมนูตามบริบท ฯลฯ) ซึ่งทำให้หน้าเว็บอยู่ในสถานะที่ไม่มีประโยชน์
การสาธิตการใช้งานแบบสด
หากต้องการดูตัวอย่างทั้งหมดจากบทความนี้อีกครั้งในที่เดียว โปรดดูการสาธิตแบบฝังที่ด้านล่าง
ขอขอบคุณ
รูปภาพหลักโดย Tom Wilson ใน Unsplash