不谈月色 發表於 2025-7-1 09:31:00

Web前端入门第 74 问:JavaScript 事件冒泡与事件捕获

<p>在讨论冒泡和捕获之前,先看这么一段代码:</p>
<pre><code class="language-html">&lt;style&gt;
.bd {
    border: 1px solid #000;
    padding: 8px;
}
&lt;/style&gt;

&lt;div id="container1" class="bd"&gt;
外层
&lt;div id="container2" class="bd"&gt;
    内层
    &lt;div id="container3" class="bd"&gt;
      最内层
      &lt;div id="container4" class="bd"&gt;
      按钮
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;script&gt;
(() =&gt; {
    const container1 = document.querySelector('#container1')
    const container2 = document.querySelector('#container2')
    const container3 = document.querySelector('#container3')
    const container4 = document.querySelector('#container4')
    container1.addEventListener('click', () =&gt; {
      console.log('container1')
    })
    container2.addEventListener('click', () =&gt; {
      console.log('container2')
    })
    container3.addEventListener('click', () =&gt; {
      console.log('container3')
    })
    container4.addEventListener('click', () =&gt; {
      console.log('container4')
    })
})()
&lt;/script&gt;
</code></pre>
<p>页面渲染大概长这样:</p>
<p><img src="https://img2024.cnblogs.com/blog/596097/202507/596097-20250701093052414-1717675739.png"></p>
<p>点击最里面的 <code>按钮</code> 元素,按照思维惯例,是否应该先执行 <code>container1</code> 的点击事件??毕竟 container1 是最外层,而且也是最先绑定事件的元素。</p>
<p>然而控制台输出结果为:</p>
<pre><code class="language-bash">container4
container3
container2
container1
</code></pre>
<p>有点出乎意料是吧,为什么先执行的是 <code>container4</code> 呢?</p>
<h2 id="事件冒泡">事件冒泡</h2>
<p>JS 绑定的事件默认是冒泡规则,什么意思呢?可以理解为:触发事件后就像水里面有一个泡泡,在水底慢慢的往上冒,从触发事件的目标元素开始,经过一层一层的盒模型,分别触发盒模型身上绑定的事件。</p>
<p>所以上面代码中,在点击 <code>按钮</code> 时,先触发了本身绑定的 click 事件,再一层一层往外传播,最终就打印出了控制台输出的结果。</p>
<h2 id="事件捕获">事件捕获</h2>
<p>注意:仅默认状态下,事件是冒泡规则,在绑定事件时候,可以修改第三个配置参数改为由外向内传播,这种传播顺序就是 <code>事件捕获</code> 。</p>
<p>以上面代码为蓝本,仅添加 addEventListener 的第三个参数为 true,就将绑定规则改为了 <code>事件捕获</code> 。如下:</p>
<pre><code class="language-js">container1.addEventListener('click', () =&gt; {
console.log('container1')
}, true)
container2.addEventListener('click', () =&gt; {
console.log('container2')
}, true)
container3.addEventListener('click', () =&gt; {
console.log('container3')
}, true)
container4.addEventListener('click', () =&gt; {
console.log('container4')
}, true)
</code></pre>
<p>还是点击 <code>按钮</code>,上面代码执行结果:</p>
<pre><code class="language-bash">container1
container2
container3
container4
</code></pre>
<p>事件捕获还有另一种写法,第三个参数可以传入一个对象,通过对象的 <code>capture</code> 属性设置为事件捕获。</p>
<pre><code class="language-js">container1.addEventListener('click', () =&gt; {
console.log('container1')
}, {
// 另一种设置事件捕获方式
capture: true,
})
</code></pre>
<h2 id="冒泡与捕获顺序">冒泡与捕获顺序</h2>
<p>既然同一个事件有冒泡与捕获,那么冒泡与捕获的顺序如何?上例子:</p>
<pre><code class="language-js">container1.addEventListener('click', () =&gt; {
console.log('冒泡:', 'container1')
})
container2.addEventListener('click', () =&gt; {
console.log('冒泡:', 'container2')
})
container3.addEventListener('click', () =&gt; {
console.log('冒泡:', 'container3')
})
container4.addEventListener('click', () =&gt; {
console.log('冒泡:', 'container4')
})
container1.addEventListener('click', () =&gt; {
console.log('捕获:', 'container1')
}, true)
container2.addEventListener('click', () =&gt; {
console.log('捕获:', 'container2')
}, true)
container3.addEventListener('click', () =&gt; {
console.log('捕获:', 'container3')
}, true)
container4.addEventListener('click', () =&gt; {
console.log('捕获:', 'container4')
}, true)
</code></pre>
<p>同时给元素绑定两种事件,点击 <code>按钮</code> 执行结果:</p>
<pre><code class="language-bash">捕获: container1
捕获: container2
捕获: container3
捕获: container4
冒泡: container4
冒泡: container3
冒泡: container2
冒泡: container1
</code></pre>
<p>到这里已经可以得出结论:JS 的事件传播会经历三个阶段,由 <code>事件捕获</code> 开始,传递到 <code>目标元素</code> 之后,就改为 <code>事件冒泡</code>,冒泡阶段完了之后事件结束。</p>
<h2 id="阻止事件传播">阻止事件传播</h2>
<p>既然事件有传播,那程序就有办法阻止事件传播。所有事件执行时都有一个 <code>event</code> 对象,此对象中有方法可用于阻止事件传播。</p>
<p>示例:</p>
<pre><code class="language-js">container1.addEventListener('click', () =&gt; {
console.log('冒泡:', 'container1')
})
container2.addEventListener('click', () =&gt; {
console.log('冒泡:', 'container2')
})
container3.addEventListener('click', () =&gt; {
console.log('冒泡:', 'container3')
})
container4.addEventListener('click', () =&gt; {
console.log('冒泡:', 'container4')
})
container1.addEventListener('click', (event) =&gt; {
event.stopPropagation()
console.log('捕获:', 'container1')
}, true)
container2.addEventListener('click', () =&gt; {
console.log('捕获:', 'container2')
}, true)
container3.addEventListener('click', () =&gt; {
console.log('捕获:', 'container3')
}, true)
container4.addEventListener('click', () =&gt; {
console.log('捕获:', 'container4')
}, true)
</code></pre>
<p><strong>注意 <code>event.stopPropagation()</code> 这个方法,此方法是阻止事件传播的关键。</strong></p>
<p>以上代码在 container1 这个元素上就阻止了事件传播,所以点击 <code>按钮</code> 之后,仅 container1 会执行,其他所有元素都不会再触发,结果:</p>
<pre><code class="language-bash">捕获: container1
</code></pre>
<p>调用 event.stopPropagation() 就是告诉 JS,事件到此为止,不再继续了。</p>
<hr>
<p><strong>event 对象其他常用方法和属性:</strong></p>
<p>event.target:触发事件的原始元素。<br>
event.currentTarget:当前绑定事件的元素(等同于 this)。<br>
event.type:事件类型(如 "click")。<br>
event.preventDefault():阻止默认行为(如表单提交、链接跳转、自定义右键菜单)。<br>
event.stopPropagation():阻止事件冒泡。<br>
event.stopImmediatePropagation():阻止同一元素的其他监听器执行。<br>
event.x 和 event.y:鼠标点击位置的坐标。</p>
<hr>
<p>在事件中要使用 <code>this</code> 获取元素时,必须使用 function 函数,使用箭头函数绑定的事件 <code>this</code> 将会指向外层作用域的 this 指针,如下代码中箭头函数 <code>this</code> 指向的是 <code>Window</code> :</p>
<pre><code class="language-html">&lt;div id="container4" class="bd"&gt;
按钮
&lt;/div&gt;

&lt;script&gt;
(() =&gt; {
    const container4 = document.querySelector('#container4')
    container4.addEventListener('click', () =&gt; {
      console.log(this) // Window 对象
    })
    container4.addEventListener('click', function () {
      console.log(this) // div#container4
    })
})()
&lt;/script&gt;
</code></pre>
<h2 id="写在最后">写在最后</h2>
<p>编程中的细节问题,总是越挖掘越心惊,学得越来越多,才会发现知道的越来越少。</p>


</div>
<div id="MySignature" role="contentinfo">
    <p>&nbsp;</p>
<p style="font-size: 18px;font-weight: bold;">文章首发于微信公众号【<span style="color:rgb(255, 71, 87)">前端路引</span>】,欢迎 <span style="color:#4ec259">微信扫一扫</span> 查看更多文章。</p>
<p>
<img style="max-width: 320px;" src="https://images.cnblogs.com/cnblogs_com/linx/2447020/o_250228035031_%E5%85%AC%E4%BC%97%E5%8F%B7%E4%BA%8C%E7%BB%B4%E7%A0%81.png"/>
</p>
<p>本文来自博客园,作者:前端路引,转载请注明原文链接:https://www.cnblogs.com/linx/p/18959113</p>
<p>&nbsp;</p><br><br>
来源:https://www.cnblogs.com/linx/p/18959113
頁: [1]
查看完整版本: Web前端入门第 74 问:JavaScript 事件冒泡与事件捕获