Javascript擒获阶段和冒泡阶段(DOM2级事件,firefox,chrome,ie9,safari)
DOM2级事件流规定的事件包括三个阶段:事件捕获阶段、处于目标阶段和事件冒泡阶段。
首先发生的是事件捕获。然后是实际的目标接收到事件。最后一个阶段是冒泡阶段。
addEventListener第一个参数是事件名,不带on前缀,第二个参数是绑定的函数,这个函数带有唯一的参数event,第三个参数为isCapture,是否捕获阶段执行的含义。当设置为false时在冒泡阶段执行,是我们常用的方式。因为我们可以对内层DIV绑定的事件函数中,执行event.stopPropagation()来阻止事件继续传递。
给一段JS示例代码:
<!DOCTYPE html> <html> <head> <title>js测试</title> </head> <body> <div id="outer"> 外层DIV <div id="inner">内层DIV</div> </div> <script> var outer = document.getElementById('outer'); var inner = document.getElementById('inner'); /** * addEventListener 为Dom元素绑定事件 * 参数1:事件名称(不以on开头) * 参数2:事件执行的函数 * 参数3:是否按捕获阶段执行,一般设置为false,冒泡阶段执行 * 说明: * DOM2级事件规定的事件流包括三个阶段。 * 第一个阶段是事件捕获阶段,从外往内传递; * 第二个阶段是处于目标阶段,这个阶段中参数3设置false或true都一样,按照绑定先后顺序执行。 * 第三个阶段是冒泡阶段,从内往外传递。 * 注意: * 以下的注释都针对点击一次“内层DIV” */ outer.addEventListener('click', function(event){ console.log('outer clicked! false 1'); // 最后执行,因为处于最外层的冒泡阶段,但如果点击“外层DIV”则最先执行 }, false); outer.addEventListener('click', function(event){ console.log('outer clicked! true 2'); // 最先执行因为处于外层捕获阶段,但如果点击“外层DIV”则第2执行 }, true); inner.addEventListener('click', function(event){ console.log('inner clicked! false 1'); // 第2执行,因为是目标 }, false); inner.addEventListener('click', function(event){ console.log('inner clicked! true 2'); // 第3执行,因为是目标 }, true); inner.addEventListener('click', function(event){ console.log('inner clicked! false 3'); // 第4执行,因为是目标 }, false); </script> </body> </html>
在上述代码中,打开控制台,如果点击“内层DIV”文字,会得到如下执行结果:
addEventListener 函数第三个参数设置为false是最常用的,绑定的函数将在冒泡阶段执行,设置为true则是在捕获阶段执行,但如果事件发生时,该DOM元素是目标元素,那么绑定函数则是在目标阶段执行,这时设置的true或false就毫无意义了,函数会按照绑定先后顺序执行。
这里可以看到,最外层DIV在捕获阶段的事件绑定函数最先得到执行。
然后是目标DIV按照先后顺序绑定的事件函数(跟第三个参数为false还是true毫无关系)。
最后才是最外层DIV的冒泡阶段绑定函数。
如果点击“外层DIV”,则得到如下结果:
这是执行顺序则是按绑定顺序来的,因为这时候两个函数已经不是针对冒泡阶段或捕获阶段了,都是目标阶段执行的。
事件传递顺序是: window -- document -- html -- body -- div ...
我们给代码增加window绑定事件,将看到,window处于最外层,捕获阶段最先执行,冒泡阶段最后执行。
<!DOCTYPE html> <html> <head> <title>js测试</title> </head> <body> <div id="outer"> 外层DIV <div id="inner">内层DIV</div> </div> <script> var outer = document.getElementById('outer'); var inner = document.getElementById('inner'); /** * addEventListener 为Dom元素绑定事件 * 参数1:事件名称(不以on开头) * 参数2:事件执行的函数 * 参数3:是否按捕获阶段执行,一般设置为false,冒泡阶段执行 * 说明: * DOM2级事件规定的事件流包括三个阶段。 * 第一个阶段是事件捕获阶段,从外往内传递; * 第二个阶段是处于目标阶段,这个阶段中参数3设置false或true都一样,按照绑定先后顺序执行。 * 第三个阶段是冒泡阶段,从内往外传递。 * 注意: * 以下的注释都针对点击一次“内层DIV” */ outer.addEventListener('click', function(event){ console.log('outer clicked! false 1'); // 最后执行,因为处于最外层的冒泡阶段,但如果点击“外层DIV”则最先执行 }, false); outer.addEventListener('click', function(event){ console.log('outer clicked! true 2'); // 最先执行因为处于外层捕获阶段,但如果点击“外层DIV”则第2执行 }, true); inner.addEventListener('click', function(event){ console.log('inner clicked! false 1'); // 第2执行,因为是目标 }, false); inner.addEventListener('click', function(event){ console.log('inner clicked! true 2'); // 第3执行,因为是目标 }, true); inner.addEventListener('click', function(event){ console.log('inner clicked! false 3'); // 第4执行,因为是目标 }, false); window.addEventListener('click', function(event){ console.log('window clicked! false'); }, false); window.addEventListener('click', function(event){ console.log('window clicked! true'); }, true); </script> </body> </html>
点击“内层DIV”,可以得到如下结果:
如果我们在window的捕获阶段事件绑定函数中增加event.stopPropagation();那么事件被扼杀在这个阶段。代码如下:
<!DOCTYPE html> <html> <head> <title>js测试</title> </head> <body> <div id="outer"> 外层DIV <div id="inner">内层DIV</div> </div> <script> var outer = document.getElementById('outer'); var inner = document.getElementById('inner'); /** * addEventListener 为Dom元素绑定事件 * 参数1:事件名称(不以on开头) * 参数2:事件执行的函数 * 参数3:是否按捕获阶段执行,一般设置为false,冒泡阶段执行 * 说明: * DOM2级事件规定的事件流包括三个阶段。 * 第一个阶段是事件捕获阶段,从外往内传递; * 第二个阶段是处于目标阶段,这个阶段中参数3设置false或true都一样,按照绑定先后顺序执行。 * 第三个阶段是冒泡阶段,从内往外传递。 * 注意: * 以下的注释都针对点击一次“内层DIV” */ outer.addEventListener('click', function(event){ console.log('outer clicked! false 1'); // 最后执行,因为处于最外层的冒泡阶段,但如果点击“外层DIV”则最先执行 }, false); outer.addEventListener('click', function(event){ console.log('outer clicked! true 2'); // 最先执行因为处于外层捕获阶段,但如果点击“外层DIV”则第2执行 }, true); inner.addEventListener('click', function(event){ console.log('inner clicked! false 1'); // 第2执行,因为是目标 }, false); inner.addEventListener('click', function(event){ console.log('inner clicked! true 2'); // 第3执行,因为是目标 }, true); inner.addEventListener('click', function(event){ console.log('inner clicked! false 3'); // 第4执行,因为是目标 }, false); window.addEventListener('click', function(event){ console.log('window clicked! false'); }, false); window.addEventListener('click', function(event){ event.stopPropagation(); console.log('window clicked! true'); }, true); </script> </body> </html>
执行结果如下:
注意:addEventListener在IE8中无效,IE8中只能使用attachEvent,对于attachEvent函数绑定事件函数时,只支持两个参数,这两个参数同addEventListener的前两个参数,第一个是事件名,但事件名比addEventListener来说要多一个前缀on,第二个是事件绑定函数,这个函数带有唯一的参数event。但attachEvent绑定的事件函数因为没有第三个参数,他们都是在冒泡阶段执行,而且默认先后顺序跟addEventListener正好相反,先绑定的后执行,后绑定的先执行。