事件流有事件捕獲和事件冒泡兩種方式,利用事件流的這個特色咱們能夠設置事件代理。事件代理能夠減小事件處理器的數量,提升 JS 腳本的性能。javascript
事件處理器就是當瀏覽器(BOM)或者 HTML 某個元素(DOM)觸發某個事件的時候所執行的函數,事件處理器也叫事件監聽器(handler == listener)。常見的事件有 load / click / mouseover / mouseout 等,下面咱們都以 click 事件舉例。html
HTML 元素是逐層嵌套的,對於這樣一個結構,若是父級元素和子級元素同時都有事件處理器,瀏覽器會如何處理呢?好比點擊父級元素的時候 alert(「parentEvent」),點擊子級元素的時候 alert(「childEvent」)。當用戶點擊子元素的時候,由於子元素嵌套在父級元素內,因此點擊子元素也必定會觸發父級元素的事件處理器,可是這牽扯到一個觸發順序的問題,究竟是先彈出子元素的內容呢仍是先彈出父元素的內容呢?這就是事件流所討論的內容。java
在開發第四代瀏覽器的時候,IE4 和 Netscape4 給出了兩個徹底相反的思考方式。IE 支持事件冒泡,而 Netscape 支持事件捕獲。那麼什麼又是事件冒泡,什麼又是事件捕獲呢?node
事件冒泡:子元素嵌套在父元素內部,點擊子元素的時候必定同時表示點擊了父元素,這個時候,先觸發子元素的事件處理器,而後再觸發父元素的事件處理器,若是父元素的父元素還有處理器,就一直向上觸發,一直到 body 元素。就像魚吐泡泡同樣,從水下向水面走,每向上走一層就會查看這一層有沒有事件處理器,若是有的話就會觸發,若是沒有的話就繼續向上尋找,直到頂層的 body,才結束尋找事件。segmentfault
事件捕獲:事件捕獲則和事件冒泡正好相反,點擊的時候從 body 往下找,若是父級元素有事件處理器就先觸發父級元素的事件處理器,再向下一層,若是子級元素有的話就觸發子級元素的事件處理器,直到這個點擊位置的最底層,也就是咱們一般所說的 target。事件捕獲就好像一塊石頭從水面向水下沉同樣,若是這一層有事件處理器,就觸發,沒有就繼續向下沉,到下層再查看是否有事件處理器,有的話就觸發,沒有的話繼續向下,一直到最底層,這個石頭就中止了。瀏覽器
能夠理解,這兩種思路都沒有問題,只不過是思考方式不一樣而已。只不過對於大多數人而言,事件冒泡彷佛更加容易接受和理解。正是因爲有這樣的兩種事件流處理方式,因此若是你同時在父元素和子元素註冊事件的時候,在 IE4 瀏覽器中會先觸發子元素的事件,而後再觸發父元素的事件。可是在 Netscape4 瀏覽器中,則會先觸發父元素的事件再觸發子元素的事件。函數
這樣的兩種事件流處理方式,也致使了瀏覽器兼容性的問題,開發者開發的同一個網頁,在不一樣的瀏覽器上卻會產生不一樣的效果。爲了解決這個問題,DOM 2級規範統一了事件流的過程,總共分爲三個階段:事件捕獲、在目標元素上,事件冒泡。DOM 2級規範將事件捕獲和事件冒泡都收入本身的囊中,因此你能夠在一個元素上同時註冊事件捕獲和事件冒泡,也就是說你能夠選擇父級元素事件處理器後觸發,也能夠選擇先觸發,甚至能夠選擇先觸發父級元素的捕獲事件,再觸發父級元素的冒泡事件。聽上去彷佛很複雜,那麼 DOM 2 級規範到底如何實現這個效果的呢?性能
DOM 2級規範在全部的 HTML 元素上都定義了兩個方法: addEventListener() 和 removeEventListener()。這兩個方法都接收三個參數:事件名稱、事件處理器函數和一個布爾值。前兩個參數不做解釋,最後一個布爾值則是決定這個事件的事件流處理方式是什麼?默認狀況下布爾值是 false,表示事件處理器是在冒泡階段觸發。當布爾值爲 true 的時候則事件處理器是在捕獲階段觸發。ui
var outer = document.getElementById("outer"), inner = document.getElementById("inner"); var outHandler = function() { alert("outer") } var innerHandler = function() { alert("inner") } //狀況一:點擊 inner 的時候,會先彈出 inner,後彈出 outer outer.addEventListener("click",outHandler,false); inner.addEventListener("click",innerHandler,false); //狀況二:點擊 inner 的時候,會先彈出 outer,後彈出 inner outer.addEventListener("click",outHandler,true); inner.addEventListener("click",innerHandler,true);
那麼有些同窗會說,個人事件處理器不是經過 DOM 2 級規範的方式添加的,是經過 DOM 0 級規範添加的,像下面這樣:spa
var outer = document.getElementById("outer"), inner = document.getElementById("inner"); var outHandler = function() { alert("outer") } var innerHandler = function() { alert("inner") } outer.onclick = outHandler; inner.onclick = innerHandler;
那這種狀況是先彈出 outer 仍是先彈出 inner 呢?根據 DOM 0 級規範,這種添加事件處理器的方式會在冒泡階段觸發事件處理器。因此說若是你想控制事件處理器是在冒泡階段觸發仍是在捕獲階段觸發,只能經過 DOM 2級規範規定的方式來添加事件處理器,若是你很肯定本身的事件處理器就是在冒泡階段才能觸發,那麼你也可使用 DOM 0 級規範的方式。
當事件捕獲和事件冒泡一塊兒存在的狀況,事件又是如何觸發呢。
對於大多數狀況,一個 HTML 元素只須要註冊一個事件,可是有時候,一些 HTML 元素須要註冊兩個及以上的事件,並且頗有可能,一個元素既須要在捕獲階段註冊事件,又須要在冒泡階段註冊事件。這種狀況又該如何處理呢?
首先,若是想要在一個 HTML 元素上同時註冊兩個事件,必須用 DOM 2級規範的方式來進行事件註冊,而不能用 DOM 0 級規範的方式,由於 DOM 0 級規範的方式添加兩個事件的話,後面的事件處理器會覆蓋前面的事件處理器,也就是說 DOM 0級的方式不管怎麼處理,都只能觸發一個事件處理器。可是 DOM 2級規範的方式則不一樣,能夠在一個元素上添加多個事件處理器,並且這多個事件處理器還能夠按照捕獲和冒泡不一樣階段添加。也就是說能夠在捕獲階段添加多個事件處理器,也能夠同時在冒泡階段添加多個事件處理器。那麼具體這些添加的事件到底按照什麼樣的順序執行呢?
這個方面的內容,我參考的文章有詳細的說明和解釋,下面的內容主要就是摘錄參考文章中的內容,你們能夠一口氣看完本文,一鼓作氣,避免精力分散。看完本文有興趣再去看參考文章,參考文章在本文的最後給出了連接。
這裏記被點擊的DOM節點爲target節點
總結下就是:
對於target節點則是先執行先註冊的事件,不管冒泡仍是捕獲
<div id="s1">s1 <div id="s2">s2</div> </div> <script> s1.addEventListener("click",function(e){ console.log("s1 冒泡事件"); },false); s2.addEventListener("click",function(e){ console.log("s2 冒泡事件"); },false); s1.addEventListener("click",function(e){ console.log("s1 捕獲事件"); },true); s2.addEventListener("click",function(e){ console.log("s2 捕獲事件"); },true); </script>
這裏大致分析下執行結果
點擊s2,click事件從document->html->body->s1->s2(捕獲前進)
這裏在s1上發現了捕獲註冊事件,則輸出"s1 捕獲事件"
到達s2,已經到達目的節點,
s2上註冊了冒泡和捕獲事件,先註冊的冒泡後註冊的捕獲,則先執行冒泡,輸出"s2 冒泡事件"
再在s2上執行後註冊的事件,即捕獲事件,輸出"s2 捕獲事件"
下面進入冒泡階段,按照s2->s1->body->html->documen(冒泡前進)
在s1上發現了冒泡事件,則輸出"s1 冒泡事件"
red
yellow
blue
若是點擊頁面中的li元素,而後輸出li當中的顏色,咱們一般會這樣寫:
(function(){ var color_list = document.getElementById('color-list'); var colors = color_list.getElementsByTagName('li'); for(var i=0;i<colors.length;i++){ colors[i].addEventListener('click',showColor,false); }; function showColor(e){ var x = e.target; console.log("The color is " + x.innerHTML); }; })();
利用事件流的特性,咱們只綁定一個事件處理函數也能夠完成:
(function(){ var color_list = document.getElementById('color-list'); color_list.addEventListener('click',showColor,false); function showColor(e){ var x = e.target; if(x.nodeName.toLowerCase() === 'li'){ console.log('The color is ' + x.innerHTML); } } })();
使用事件代理的好處不只在於將多個事件處理函數減爲一個,並且對於不一樣的元素能夠有不一樣的處理方法。假如上述列表元素當中添加了其餘的元素(如:a、span等),咱們沒必要再一次循環給每個元素綁定事件,直接修改事件代理的事件處理函數便可。
在處理事件代理的時候,事件 event 有兩個比較特殊的屬性,event.target 和 event.currentTarget,這兩個屬性又有什麼區別呢?
event.target 是觸發事件的元素,而 event.currentTarget 是事件綁定的元素。也就是說,大部分狀況下,當使用事件代理時,event.target 是子元素,而 event.currentTarget 是父級元素。
red
yellow
blue
當點擊任何一個 li 的時候,首先會彈出目標元素,也就是子元素的 id,而後纔會彈出事件綁定元素的 id,也就是父級元素的 id。