本文總結了之前模糊的一些概念,算是概念整理。首先聲明,當目標對象是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>
事件捕獲提供了在事件沒送達目標之前查看它們的機會,用於程序調試,或取消事件,過濾掉事件從而使目標事件絕不會被調用,常用於處理鼠標拖放,因為要處理拖放事件的位置不能是這個元素內部的子元素。