先看這個固定例子
固定點擊落在預覽按鈕內的 span,監聽器卻掛在 tools。先找最近的操作按鈕,再讀 data-action。
<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="tools"><button type="button" data-action="preview"><span id="label">預覽原始檔</span></button><p id="plain">一般筆記</p></section><output id="result"></output>
<script>const tools=document.querySelector('#tools');
tools.addEventListener('click',event=>{const button=event.target.closest('button[data-action]');if(button&&tools.contains(button))document.querySelector('#result').textContent=button.dataset.action;});
document.querySelector('#label').click();console.log(document.querySelector('#result').textContent);
</script>preview| 核對項目 | 所給輸入或動作 | 結果 |
|---|---|---|
| target | #label span | 沒有 data-action |
| currentTarget | #tools section | 監聽器所屬 |
| closest | button[data-action] | preview |
原生機制怎樣處理
target 是最初接收事件的節點,回呼內的 currentTarget 是掛監聽器的容器。closest 由自己向祖先找,contains 再核按鈕仍在 tools。本例目標確定是 Element;一般回呼應先核這個條件。
比較另一寫法
直接讀 target.dataset.action,span 沒有該屬性,得到 undefined;preview 在外層按鈕。
<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="tools"><button type="button" data-action="preview"><span id="label">預覽原始檔</span></button></section><output id="result"></output>
<script>let observed;document.querySelector('#tools').addEventListener('click',event=>{observed=event.target.dataset.action;});document.querySelector('#label').click();
const result={actionType:typeof observed};
document.querySelector('#result').textContent=JSON.stringify(result);
console.log(JSON.stringify(result));
</script>{"actionType":"undefined"}試清邊界
按一般段落會否寫入操作?直接按按鈕又是否有效?
查看參考答案
一般段落被略過。按鈕自身也符合 closest,會取到 preview;記錄可分清兩個目標及共用容器。
<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="tools"><button id="action" type="button" data-action="preview"><span id="label">預覽原始檔</span></button><p id="plain">一般筆記</p></section><output id="result"></output>
<script>const tools=document.querySelector('#tools');const trace=[];tools.addEventListener('click',event=>{const button=event.target.closest('button[data-action]');trace.push({target:event.target.id,currentTarget:event.currentTarget.id,action:button&&tools.contains(button)?button.dataset.action:null});});
document.querySelector('#plain').click();document.querySelector('#action').click();const result=trace;
document.querySelector('#result').textContent=JSON.stringify(result);
console.log(JSON.stringify(result));
</script>[{"target":"plain","currentTarget":"tools","action":null},{"target":"action","currentTarget":"tools","action":"preview"}]