JS 中的事件冒泡与捕获

zdmw6298 8年前
   <p>刚接触 JS 的那个时候,啥也不懂,只想着如何利用 Google、百度到的函数来解决实际的问题,不会想到去一探究竟。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/f1bae73dd687b146abd4d4d48478913f.jpg"></p>    <p>渐渐的,对 JS 的语言的不断深入,有机会去了解一些原理性东西。最近在看 JQuery 源码,感触很多,总想着用原生的 JS 去实现自己的一个 JQuery 库。说实在的,JQuery 里面很多函数和思路,是千百开源工作者长期的贡献,哪能是短时间就能消化的了。</p>    <p>最近再次碰到 addEventListener 函数,由于之前并没有弄懂第三个参数的含义,要么默认值,要么手动设置成 false。这次看了不少文章,彻底把事件冒泡和捕获弄懂。</p>    <h2><strong>什么事件冒泡与捕获</strong></h2>    <p>事件冒泡与捕获是 DOM 中事件传播的两种方式,比如说对于注册了相同事件的两个 DOM 元素(简单点就是两个 div,一里一外),当点击里层 div 的时候,这两个事件谁先执行。</p>    <p>冒泡事件,由里向外,最里层的元素先执行,然后冒泡到外层。</p>    <p>捕获事件,由外向里,最外层的元素先执行,然后传递到内部。</p>    <p>在 IE 9 之前是只支持事件冒泡,IE 9(包括 IE 9) 之后和目前主流的浏览器都同时支持两种事件。</p>    <p>如何设置,只需修改 addEventListener 的第三个参数,true 为捕获,false 为冒泡,默认为冒泡。</p>    <p>举个简单的例子,</p>    <table>     <tbody>      <tr>       <td> <pre>  <code class="language-javascript"><div>    <span class="out">      <span class="in"></span>    </span>  </div>  <script type="text/javascript">    var dom_out = document.getElementsByClassName('out')[0];    var dom_in = document.getElementsByClassName('in')[0];    dom_out.addEventListener('click',function(){      alert('out');    },false);    dom_in.addEventListener('click',function(){      alert('in');    },false);  </script></code></pre> </td>      </tr>     </tbody>    </table>    <p style="text-align:center"> </p>    <p>out in</p>    <p>在上面这个例子中,事件是按照冒泡来执行的,点击里层的 in ,会看到先 alert 的顺序是先 “in” 后 “out”,如果把事件改成捕获, alert 的顺序又不一样了。</p>    <table>     <tbody>      <tr>       <td> <pre>  <code class="language-javascript"><script type="text/javascript">    var dom_out = document.getElementsByClassName('out')[0];    var dom_in = document.getElementsByClassName('in')[0];    dom_out.addEventListener('click',function(){      alert('out');    },true);    dom_in.addEventListener('click',function(){      alert('in');    },true);  </script></code></pre> </td>      </tr>     </tbody>    </table>    <p>out2 in2</p>    <p>上面这个例子是捕获事件的例子,点击 in 效果是不是不一样呢?</p>    <p>之所以会有冒泡和捕获事件(像 IE 9 之前的浏览器不支持捕获事件,还真是反程序员),毕竟在实际中处理事情肯定有个先后顺序,要么由里向外,要么由外向里,两者都是必须的。</p>    <p>但有时候为了兼容 IE 9 以下版本的浏览器,都会把第三个参数设置成 false 或者默认(默认就是 false)。</p>    <h2><strong>进一步理解冒泡和捕获</strong></h2>    <p>现在已经说清楚冒泡和捕获,那么如果同时出现冒泡和捕获会出现什么结果?</p>    <p>原来浏览器处理时间分为两个阶段,捕获阶段和冒泡阶段,</p>    <ul>     <li> <p>先执行捕获阶段,如果事件是在捕获阶段执行的(true 情况),则执行;</p> </li>     <li> <p>然后是冒泡阶段,如果事件是在冒泡阶段执行的(false 情况),则执行;</p> </li>    </ul>    <p>来看一看例子就知道了:</p>    <table>     <tbody>      <tr>       <td> <pre>  <code class="language-javascript"><div>    <span class="s1">s1      <span class="s2">s2        <span class="s3">s3        </span>      </span>    </span>  </div></code></pre> </td>      </tr>     </tbody>    </table>    <p>这次我们设置三个 span,分别是 s1, s2, s3,然后设置 s1,s3 为冒泡执行,s2 为捕获执行:</p>    <table>     <tbody>      <tr>       <td> <pre>  <code class="language-javascript"><script type="text/javascript">    var s1 = document.getElementsByClassName('s1')[0];    var s2 = document.getElementsByClassName('s2')[0];    var s3 = document.getElementsByClassName('s3')[0];    s1.addEventListener('click',function(){      alert('s1');    },false);    s2.addEventListener('click',function(){      alert('s2');    },true);    s3.addEventListener('click',function(){      alert('s3');    },false);  </script></code></pre> </td>      </tr>     </tbody>    </table>    <p>s1 s2 s3</p>    <p>从运行的效果来看,点击 s3,依次 alert s2 => s3 => s1,说明:</p>    <ul>     <li> <p>捕获事件和冒泡事件同时存在的,而且捕获事件先执行,冒泡事件后执行;</p> </li>     <li> <p>如果元素存在事件且事件的执行时间与当前逻辑一致(冒泡或捕获),则执行。</p> </li>    </ul>    <h2><strong>默认事件取消与停止冒泡</strong></h2>    <p>当然,有时候我们只想执行最内层或最外层的事件,根据内外层关系来把范围更广的事件取消掉(对于新手来说,不取消冒泡,很容易中招的出现 bug)。 event.stopPropagation() (IE 中 window.event.cancelBubble = true )可以用来取消事件冒泡。</p>    <p>有时候对于浏览器的默认事件也需要取消,这时候用到的函数则是 event.preventDefault() (IE 中 window.event.returnValue = false )。</p>    <p>那么默认事件取消和停止冒泡有什么区别呢?我的理解:浏览器的默认事件是指浏览器自己的事件(这不废话吗),比如 a 标签 的点击,表单的提交等,取消掉就不会执行啦;冒泡则取消的是由外向里(捕获)、由里向外(冒泡),stop 之后,就不会继续遍历了。stackoverflow 上的 解答</p>    <p>看下例子,依旧是上面那个例子,不过每个函数都加了 停止冒泡:</p>    <table>     <tbody>      <tr>       <td> <pre>  <code class="language-javascript">s1.addEventListener('click',function(e){    e.stopPropagation();    alert('s1');  },false);  s2.addEventListener('click',function(e){    e.stopPropagation();    alert('s2');  },true);  s3.addEventListener('click',function(e){    e.stopPropagation();    alert('s3');  },false);</code></pre> </td>      </tr>     </tbody>    </table>    <p>s1 s2 s3</p>    <p>点击的结果是:当点击 s2 或 s3 的时候,都会 alert s2,点击 s1,弹出 s1。因为事件被取消的缘故,点击 s3,执行 s2后就不会在向下执行了。</p>    <p>在看一个 preventDefault 的例子。</p>    <table>     <tbody>      <tr>       <td> <pre>  <code class="language-javascript"><div>    <a href="/">点我回主页</a>  </div>  <div>    <a href="/" class="back">点我不回主页</a>  </div>  <script type="text/javascript">    var back = document.getElementsByClassName('back')[0];    back.addEventListener('click', function(e){      e.preventDefault();    });  </script></code></pre> </td>      </tr>     </tbody>    </table>    <p>第二个链接是不是回不了主页,因为浏览器的默认事件被取消了。</p>    <p>以上所有例子请在非低版本 IE 浏览器的环境下浏览</p>    <h2><strong>总结</strong></h2>    <p>总结就补充两个兼容 IE 的函数吧:</p>    <table>     <tbody>      <tr>       <td> <pre>  <code class="language-javascript">function stopBubble(e) {    //如果提供了事件对象,则这是一个非IE浏览器    if ( e && e.stopPropagation )        //因此它支持W3C的stopPropagation()方法        e.stopPropagation();    else        //否则,我们需要使用IE的方式来取消事件冒泡        window.event.cancelBubble = true;  }  //阻止浏览器的默认行为  function stopDefault( e ) {    //阻止默认浏览器动作(W3C)    if ( e && e.preventDefault )        e.preventDefault();    //IE中阻止函数器默认动作的方式    else        window.event.returnValue = false;    return false;  }</code></pre> </td>      </tr>     </tbody>    </table>    <p> </p>    <p> </p>    <p>来自:http://yuren.space/blog/2016/10/16/事件冒泡与捕获/</p>    <p> </p>