马黑黑 发表于 2026-3-10 12:57

理解JS事件冒泡

<style>
        .artBox { margin: auto; max-width: 1200px; font: normal 20px/1.5 Simsun, NSimsun, sans-serif;position: relative; }
        .artBox p { margin: 12px 0; }
        .artBox > blockquote { margin: 12px 2em; padding: 8px 8px 8px 16px; border-left: 4px solid gray; background: beige; }
        .artBox mark { padding: 2px 8px; background: aliceblue; }
</style>

<div class="artBox">
        <p>当 html 元素彼此间存在“血亲关系”,即它们层层嵌套,那么,最里层的元素发起人机交互动作时,外层的任意一层都可以接收到这个事件。可以将层层嵌套的元素想象成俄罗斯套娃:你戳一下最里层的娃娃,它外面的每一个娃娃都可以依次感知到被戳了一下。这就是所谓的“冒泡”:事件被触发后从触发点外溢,这个事件就像涟漪一样被传输到存在“直系亲属”关系的所有元素,只要它们原意监听。</p>

        <blockquote>
                <p><strong>网页“直系关系”示意图谱:</strong></p>
                <p>window → document → html → body → div(或其他元素)</p>
        </blockquote>

        <p>试看下面的代码,重点理解 html 代码:四个 div 盒子层层嵌套,它们是“四代同堂”的直系亲属关系——</p>
       
        <div class="codebox" data-prev="1">
&lt;style&gt;
    .ttDiv { margin: auto; width: 40vw; aspect-ratio: 1 / 1; border: 1px solid gray; display: grid; place-items: center; position: relative; }
    .ttDiv::after { content: attr(data-counter); position: absolute; left: 10px; top: 5px; }
    .outer1 { width: 40vw; margin-top: 100px; }
    .inner1 { width: 30vw; }
    .inner2 { width: 20vw; }
    .inner3 { width: 10vw; }
   
&lt;/style&gt;

&lt;div id="div1" class="ttDiv outer1" data-counter="0"&gt;
    &lt;div id="div2" class="ttDiv inner1" data-counter="0"&gt;
      &lt;div id="div3" class="ttDiv inner2" data-counter="0"&gt;
            &lt;div id="div4" class="ttDiv inner3" data-counter="0"&gt;&lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;script&gt;
    // 点击计数器函数
    const clicking = (element) =&gt; {
      let counter = element.dataset.counter * 1 + 1;
      element.dataset.counter = counter;
    }

    // 获取所有的 .ttDiv 盒子
    const ttDivs = document.querySelectorAll('.ttDiv');

    // 盒子被点击时触发计数
    ttDivs.forEach(ttdiv =&gt; {
      ttdiv.onclick = () =&gt; clicking(ttdiv);
    });
&lt;/script&gt;
        </div>

        <p>这组代码为每一个 div 设置了<mark>::after</mark>伪元素,用以展示被点击的次数。JS 代码设计了一个计数器函数:元素如果被点击则计数器加 1,然后每一个 div 都监听点击事件,该事件如果被触发则执行计数器函数。</p>
        <p>理解冒泡事件,可以点击代码栏右上角“预览”按钮进入预览页面,点击不同层级的 div 元素,观察计数器的变化。</p>
        <p>如你所见:冒泡是从里往外发起。如果点击的不是最里层的,那么,不被点击的里层 div 计数器不会发生变化。</p>

        <blockquote>
                <p><strong>网页元素冒泡路径示意图:</strong></p>
                <p>divs(或其它元素)→ body → html → document → window</p>
        </blockquote>
       
        <p>冒泡如果不人为阻止,它一直会往外冒,直至“直系亲属”的最后一个成员。换言之,上述“四代同堂”的 div 之外,如果还有其它的属于它们祖先的 div 或是其它容器盒子,都会接收到冒泡事件,直至DOM“族谱”的根(window对象)才会停止。</p>
        <p>阻止冒泡指特定元素的事件私有化,不允许外层的“直系亲属”捕获相关事件。这时候,可以在特定元素的指定事件(例如点击事件)中加入 <mark>event.stopPropagation()</mark>(中断传播)加以实现。前面的代码示例,每一个 div 都有 id 标识符,JS 中的元素点击事件代码可以通过识别目标的 id,如果与指定点击事件要阻止冒泡的 div 的 id 相匹配就阻隔事件的冒泡行为。可以这样改进 div 的点击事件:</p>

        <div class="codebox">
// 盒子被点击时触发计数,阻止 div4 的点击行为冒泡
ttDivs.forEach( ttdiv =&gt; {
    ttdiv.onclick = (event) =&gt; {
          if (event.target.id === 'div4') event.stopPropagation();
          clicking(ttdiv);
    }
});
        </div>

        <p>这么一来,最里层的 div 被点击时,只有它自己的计数器发生变化,外层所有元素都不再能够捕获到点击事件。而其长辈 div 没有受到限制,它们的点击事件仍然会冒泡。</p>
</div>

<script type="module">
import linenumber from 'https://638183.freep.cn/638183/web/helight/linenumber.js';
linenumber();
</script>

杨帆 发表于 2026-3-10 15:05

本帖最后由 杨帆 于 2026-3-10 15:06 编辑

理解JS事件冒泡很重要,但理解起来很抽象,感谢马老师深入浅出、言简意赅、通俗易懂的讲授{:4_180:}

红影 发表于 2026-3-10 22:12

看了预览,这些事件涟漪还真是一层层传递的呢。
有趣的冒泡事件,而且它还能被阻隔。
感谢黑黑的讲解,学习了{:4_187:}

马黑黑 发表于 2026-3-11 12:33

杨帆 发表于 2026-3-10 15:05
理解JS事件冒泡很重要,但理解起来很抽象,感谢马老师深入浅出、言简意赅、通俗易懂的讲授

{:4_190:}

马黑黑 发表于 2026-3-11 12:34

红影 发表于 2026-3-10 22:12
看了预览,这些事件涟漪还真是一层层传递的呢。
有趣的冒泡事件,而且它还能被阻隔。
感谢黑黑的讲解,学 ...

JS围绕HTML进行设计,非常不简单
页: [1]
查看完整版本: 理解JS事件冒泡