跳至內容
Astera Technology

取消預設動作,不代表事件停止向上傳

取消與傳播是兩件事,要分開核對旗標。

下載完整個案資料及參考答案 ↓

先看這個固定例子

固定 sample 事件設為可冒泡、可取消。child 呼叫 preventDefault 後,parent 是否仍收到?此自訂事件沒有導航或送表單的預設動作。

完整頁面內容與固定程式
<style>html{font-size:16px}body{margin:12px;font:1rem/1.5 system-ui,sans-serif;color:#17332d;background:#fff}p{overflow-wrap:anywhere}output{display:block;margin-block:12px;overflow-wrap:anywhere;white-space:pre-wrap}button,input{font:inherit;max-width:100%;box-sizing:border-box}button{margin:4px;padding:8px}label{display:block;margin-block:8px}button:focus-visible,input:focus-visible{outline:3px solid #23715d;outline-offset:2px}ul{padding-inline-start:24px}li{overflow-wrap:anywhere}.highlight{border:2px solid green}</style>
<section id="parent"><p id="child">本地事件目標</p></section><output id="result"></output>
<script>const trace=[];const child=document.querySelector('#child');child.addEventListener('sample',event=>{trace.push('child');event.preventDefault();});document.querySelector('#parent').addEventListener('sample',event=>trace.push('parent:'+event.defaultPrevented));
const returned=child.dispatchEvent(new Event('sample',{bubbles:true,cancelable:true}));const result={trace,returned};
document.querySelector('#result').textContent=JSON.stringify(result);
console.log(JSON.stringify(result));
</script>
確實的 console 輸出
{"trace":["child","parent:true"],"returned":false}
輸入與原生結果
核對項目所給輸入或動作結果
childpreventDefault()defaultPrevented: true
parent仍冒泡至 parentparent:true
dispatchEventcancelable: truefalse

原生機制怎樣處理

可取消事件的 preventDefault 會設定取消旗標,因此 dispatchEvent 傳回 false;它沒有設定停止傳播。stopPropagation 擋住後續祖先,卻不會自行取消。事件若不可取消,preventDefault 也不能改成已取消。

比較另一寫法

只用 stopPropagation,parent 記錄不見,但 dispatch 仍傳回 true。本例自訂事件不示範任何真實瀏覽器預設動作。

另開的反例原始碼
<style>html{font-size:16px}body{margin:12px;font:1rem/1.5 system-ui,sans-serif;color:#17332d;background:#fff}p{overflow-wrap:anywhere}output{display:block;margin-block:12px;overflow-wrap:anywhere;white-space:pre-wrap}button,input{font:inherit;max-width:100%;box-sizing:border-box}button{margin:4px;padding:8px}label{display:block;margin-block:8px}button:focus-visible,input:focus-visible{outline:3px solid #23715d;outline-offset:2px}ul{padding-inline-start:24px}li{overflow-wrap:anywhere}.highlight{border:2px solid green}</style>
<section id="parent"><p id="child">本地事件目標</p></section><output id="result"></output>
<script>const trace=[];const child=document.querySelector('#child');child.addEventListener('sample',event=>{trace.push('child');event.stopPropagation();});document.querySelector('#parent').addEventListener('sample',event=>trace.push('parent:'+event.defaultPrevented));
const returned=child.dispatchEvent(new Event('sample',{bubbles:true,cancelable:true}));const result={trace,returned};
document.querySelector('#result').textContent=JSON.stringify(result);
console.log(JSON.stringify(result));
</script>
反例輸出
{"trace":["child"],"returned":true}

試清邊界

cancelable=false、bubbles=true,preventDefault 還能改甚麼?

查看參考答案

defaultPrevented 仍是假;parent 會跑,dispatch 傳回 true。傳播旗標仍是另一回事。

另開的邊界原始碼
<style>html{font-size:16px}body{margin:12px;font:1rem/1.5 system-ui,sans-serif;color:#17332d;background:#fff}p{overflow-wrap:anywhere}output{display:block;margin-block:12px;overflow-wrap:anywhere;white-space:pre-wrap}button,input{font:inherit;max-width:100%;box-sizing:border-box}button{margin:4px;padding:8px}label{display:block;margin-block:8px}button:focus-visible,input:focus-visible{outline:3px solid #23715d;outline-offset:2px}ul{padding-inline-start:24px}li{overflow-wrap:anywhere}.highlight{border:2px solid green}</style>
<section id="parent"><p id="child">本地事件目標</p></section><output id="result"></output>
<script>const trace=[];const child=document.querySelector('#child');child.addEventListener('sample',event=>{event.preventDefault();trace.push('child:'+event.defaultPrevented);});document.querySelector('#parent').addEventListener('sample',event=>trace.push('parent:'+event.defaultPrevented));
const returned=child.dispatchEvent(new Event('sample',{bubbles:true,cancelable:false}));const result={trace,returned};
document.querySelector('#result').textContent=JSON.stringify(result);
console.log(JSON.stringify(result));
</script>
邊界輸出
{"trace":["child:false","parent:false"],"returned":true}

原始例子檔案