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 ครั้ง โดยครั้งหนึ่งด้วย capture = true
และอีกครั้งด้วย capture = false
) ก็แสดงว่าตัวแฮนเดิลเหตุการณ์ทั้ง 2 เหตุการณ์จะเริ่มทำงานสำหรับองค์ประกอบเดียวกันอย่างแน่นอน แต่สิ่งสำคัญอีกอย่างที่ควรทราบก็คือไฟเหล่านี้จะเริ่มกระจายเป็นระยะต่างๆ (ระยะแรกอยู่ในระยะแคปเจอร์ และอีกระยะในช่วงการฟอกอากาศ)
ถัดไป เหตุการณ์จะเผยแพร่ (โดยทั่วไปจะใช้คำว่า "bubble" เพราะดูเหมือนว่าเหตุการณ์กำลัง "ขึ้น" แผนผัง 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"
คุณสามารถเล่นเกมนี้แบบอินเทอร์แอกทีฟได้ในการสาธิตสดด้านล่าง คลิกองค์ประกอบ #C
และสังเกตเอาต์พุตของคอนโซล
event.stopPropagation()
เมื่อเราเข้าใจเกี่ยวกับแหล่งที่มาของเหตุการณ์และวิธีเดินทาง (เช่น การเผยแพร่) ผ่าน DOM ทั้งในขั้นตอนการจับภาพและระยะการฟองสบู่ เราจึงหันไปสนใจ event.stopPropagation()
แทน
เรียกใช้เมธอด stopPropagation()
ในเหตุการณ์ DOM แบบเนทีฟ (ส่วนใหญ่) ได้ ผมพูดว่า "ส่วนใหญ่" เพราะมี 2-3 รายการที่การเรียกใช้วิธีนี้ไม่ได้ทำอะไร (เพราะเหตุการณ์ไม่ได้เริ่มต้นจากเหตุการณ์) กิจกรรมอย่าง 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()
จะทำอะไร ดูเหมือนแอปจะทำอะไรคล้ายๆ กันอยู่นะ ใช่ไหม
ไม่ครับ แม้ว่าทั้งคู่จะมีความสับสนอยู่บ้าง แต่จริงๆ แล้วพวกเขาไม่ได้เกี่ยวข้องกันมากนัก
เมื่อเห็น 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 และดูเอาต์พุตของคอนโซล (และข้อเท็จจริงที่ว่าคุณไม่เปลี่ยนเส้นทางไปยังเว็บไซต์ 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
+ เหตุการณ์ "คีย์ดาวน์":preventDefault()
สำหรับชุดค่าผสมนี้เป็นอันตรายถึงชีวิต เนื่องจากจะทำให้หน้าแทบไม่มีประโยชน์ ทำให้ไม่สามารถเลื่อนแป้นพิมพ์ แท็บ และการไฮไลต์แป้นพิมพ์document
+ เหตุการณ์ "mousedown":preventDefault()
สำหรับชุดค่าผสมนี้จะป้องกันการไฮไลต์ข้อความด้วยเมาส์และการทำงาน "เริ่มต้น" อื่นๆ ที่ระบบจะเรียกใช้ด้วยเมาส์ลงองค์ประกอบ
<input>
+ เหตุการณ์ "keypress":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()
ด้วยเช่นกัน ซึ่งคุณจะจำไม่ได้ว่าใช้ การดำเนินการ เริ่มต้น ดังนั้นการดำเนินการเริ่มต้นทั้งหมด (เช่น การเลื่อนลูกกลิ้งเมาส์ การเลื่อนหรือไฮไลต์ด้วยแป้นพิมพ์ หรือการกด Tab การคลิกลิงก์ การแสดงเมนูบริบท ฯลฯ) จึงจะได้รับการป้องกันไม่ให้ทั้งหมด ซึ่งจะทำให้ออกจากหน้าอยู่ในสถานะที่ไม่มีประโยชน์พอสมควร
การสาธิตแบบสด
หากต้องการสำรวจตัวอย่างทั้งหมดจากบทความนี้อีกครั้งในที่เดียว ให้ดูการสาธิตแบบฝังที่ด้านล่าง
ข้อความแสดงการยอมรับ
รูปภาพหลักของ Tom Wilson ใน Unsplash