本文总结了之前模糊的一些概念,算是概念整理。首先声明,当目标对象是window对象或其他一些单独对象,eg:XMLHttpRequest时,就没事件传播啥事了,浏览器直接通过调用对象上的程序响应事件。而我们所说的事件传播是发生在事件目标是文档或文档元素的时候。
事件传播也就是我们平时说的事件流,分三个阶段:第一阶段发生在目标处理程序调用之前,就是我们通常说的"捕获”阶段;第二阶段是目标对象本身的处理程序调用;第三阶段就是冒泡阶段。我们平时关注的就是第二和第三阶段——事件捕获和事件冒泡。
我们说的事件流其实跟样式没啥关系,跟结构有关,从下面的代码就可以看到:
<!DOCTYPE HTML> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>无标题文档</title> <style> *{margin: 0; padding: 0; } div{padding:40px; } #div1{background-color: red; } #div2{background-color: green; } #div3{background-color: blue; position: absolute; top: 300px; } </style> <script> window.onload = function(){ var oDiv1 = document.getElementById('div1'), oDiv2 = document.getElementById('div2'), oDiv3 = document.getElementById('div3'); function fn1(){ alert(this.id); } function bind(obj,evname,fn){ if (obj.addEventListener) { obj.addEventListener(evname,fn,false); }else{ obj.attachEvent('on'+evname,function(){ fn.call(obj) }) } } bind(oDiv1,'click',fn1) bind(oDiv2,'click',fn1) bind(oDiv3,'click',fn1) } </script> </head> <body> <div id="div1"> <div id="div2"> <div id="div3"></div> </div> </div> </body> </html>
其实div3从样式上跟1和2没啥关系,其实结构上使他们的子孙,所以它的事件会传播给1和2,PS:在实际工作中不要向上面给元素一个个加id,以上只是为了举例方便。
闲话不表,进入文本正题,我们来聊事件冒泡和事件捕获。
事件冒泡
调用在目标元素上注册的事件处理函数后,大部分事件会“冒泡”到DOM树根,我们可以想象,就想一条鱼在水底喘了口气,然后气泡一直往上一直往上,直到到了水面。。给一个文档元素加各事件,然后这个事件会传给它的父级以及父级的父级。。传给body、传给document、传给window。这样自有它的妙用,比如我们遇到一个有大量单独文档元素的东西,每个单独文档元素都要加事件名称一样的事件,不太可能一个个加吧,多浪费时间,这时候在它们共同的祖先元素上注册一个处理程序来处理所有的事件。比如可以在form元素上注册change事件,就不用再每个元素上注册了。
代码如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <script> window.onload = function(){ var oForm = document.getElementById('f1'); oForm.onchange = function(){ alert('就是个泡泡') } } </script> </head> <body> <form action="" id="f1"> <input type="text"> <input type="radio" name="boy">男 <input type="radio" name="girl">女 <select name="city" id=""> <option value="bj">北京</option> <option value="sh">上海</option> <option value="wh">武汉</option> </select> </form> </body> </html>
值得注意的是,文档元素上的大部分事件都会这样冒泡,但scroll、focus、blur就是那么性格鲜明,它们都不管这些,不信你试试。
事件捕获
事件传播的捕获阶段,方向跟冒泡相反。最先调用的是window对象的捕获处理程序,然后是document,接着是body,一直往下,直到目标对象的父元素的捕获事件程序,在目标对象本身上注册的捕获事件处理程序不会被调用(我当时给目标对象注册了捕获事件处理程序,测试的时候那个事件居然触发了,后来想了下,那个函数调用的原因应该是冒泡而不是捕获)。
实现事件捕获,addEventListener()方法就登场啦,把布尔值作为其第三个参数传进去,若这个参数是true,那么事件处理程序就被注册为捕获事件处理程序,它会在事件传播的第一个阶段调用;若是false的话,就是事件冒泡了。事件捕获代码如下:
<!DOCTYPE HTML> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>无标题文档</title> <style> *{margin: 0; padding: 0; } div{padding:40px; } #div1{background-color: red; } #div2{background-color: green; } #div3{background-color: blue; position: absolute; top: 300px; } </style> <script> window.onload = function(){ var oDiv1 = document.getElementById('div1'), oDiv2 = document.getElementById('div2'), oDiv3 = document.getElementById('div3'); function fn1(){ alert(this.id); } oDiv1.addEventListener('click', fn1, true); oDiv2.addEventListener('click', fn1, true); oDiv3.addEventListener('click', fn1, true); } </script> </head> <body> <div id="div1"> <div id="div2"> <div id="div3"></div> </div> </div> </body> </html>
但你会发现,这种方法在ie低版本是不兼容的,ie低版本实现不了捕获,说到这里我们就回过头来说事件绑定了,我们熟悉的事件绑定方式,是obj.Event,这种方式的弊端就是,你想给一个对象加两个及以上的事件,后面的那个事件,必定会覆盖前面的那个事件,所以就有了第二种方式——attachEvent(),用这种方法不再报错了,虽然ie低版本执行顺序是反的,但起码也实现了这种功能,但是如果被传入的这个事件函数里面出现了this,这个this又会指向window,解决的办法就是用call改变this的指向,话不多少,直接来兼容后的代码:
<!DOCTYPE HTML> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>document</title> <script> function fn1() { alert(this); } function fn2() { alert(2); } function bind(obj,evname,fn){ if (obj.addEventListener) { obj.addEventListener(evname,fn,false); }else{ obj.attachEvent('on'+evname,function(){ fn.call(obj) }) } } bind(document,'click',fn1); </script> </head> <body> </body> </html>
事件捕获提供了在事件没送达目标之前查看它们的机会,用于程序调试,或取消事件,过滤掉事件从而使目标事件绝不会被调用,常用于处理鼠标拖放,因为要处理拖放事件的位置不能是这个元素内部的子元素。