JavaScript與HTML之間的交互是經過事件實現的。事件,就是文檔或瀏覽器窗口中發生的一些特定的交互瞬間。html
從頁面中接收事件的順序稱爲事件流。git
IE --> 事件冒泡流github
Netscape --> 事件捕獲流瀏覽器
查看源碼:DOM2事件-捕獲-冒泡dom
IE的事件流叫作事件冒泡(event bubbling),即事件開始時由最具體的元素(文檔中嵌套層次最深的那個節點)接收,而後逐級向上傳播到較爲不具體的節點(文檔)。函數
咱們先來個簡單的例子,這是HTML結構性能
<!DOCTYPE html> <html lang="en"> <head> <title>js事件流</title> </head> <body> <div id="div"> 我是div </div> </body> </html>
有一個div元素。若是咱們點擊<div>
元素,那麼這個click事件的順序會怎麼呢?設計
咱們給幾個元素都添加監聽事件,3d
element.addEventListener(event, function, useCapture)
參數說明:code
- event 字符串。指定事件名,好比 click、mouseenter、mouseleave
- function 函數。指定要事件觸發時執行的函數。
- useCapture 布爾值。指定事件是否在捕獲或冒泡階段執行。默認值是false,即事件在冒泡階段執行。true,在捕獲階段執行
var div = document.getElementById('div') var body = document.body var html = document.documentElement div.addEventListener('click', function () { console.log('div標籤') }, false) body.addEventListener('click', function () { console.log('body ') }, false) html.addEventListener('click', function () { console.log('html') }, false) document.addEventListener('click', function () { console.log('document') }, false)
而後點擊<div>
元素,查看控制檯輸出,
由上面的輸出結果能夠看出,這個click事件會按照以下順序傳播:
<div>
---> <body>
---> <html>
--->document
也就是說,click事件首先發生在目標元素,而後,click事件沿着DOM樹向上傳播到document對象。這就是事件冒泡。
全部現代瀏覽器都支持事件冒泡。IE九、Firefox、Chrome和Safari則將事件一直冒泡到window對象。
若是,咱們在每一個DOM元素上都設置監聽事件,會獲得的事件的傳播順序是:
<div>
---><body>
---> <html>
--->document
---> window
Netscape Communicator團隊提出的另外一種事件流叫作事件捕獲(event capturing)。事件捕獲是不太具體的節點應該更早接收到事件,而最具體的節點應該最後接收到事件。事件捕獲的用意在於在事件到達預期目標以前捕獲它。
咱們仍是能夠看剛纔的例子,DOM結構不變,咱們修改下監聽事件
var div = document.getElementById('div') var body = document.body var html = document.documentElement div.addEventListener('click', function () { console.log('div標籤') }, true) body.addEventListener('click', function () { console.log('body ') }, true) html.addEventListener('click', function () { console.log('html') }, true) document.addEventListener('click', function () { console.log('document') }, true)
以後,仍是點擊<div>
元素,獲得的結果
從上面例子能夠看出,事件走向: document
---><html>
---> <body>
---><div>
也就是說,在事件捕獲過程當中,document對象首先接收到click事件,而後事件沿着DOM樹依次向下,一直傳播到事件的實際目標,即
<div>
元素。這就是事件捕獲。與事件冒泡過程,截然相反。
儘管「DOM2級事件」規範要求事件應該從document對象開始傳播,可是現代瀏覽器大部分都是從window對象開始捕獲事件的。
若是,咱們在每一個DOM元素上都設置監聽事件,會獲得的事件的傳播順序是:
window
---> document
---><html>
---> <body>
---><div>
因爲在老版本的瀏覽器中不支持,所以事件捕獲用的人比較少,除非在特殊須要的時候才使用。
「DOM2級事件」規定的事件流包括三個階段:事件捕獲階段、處於目標階段和事件冒泡階段。
首先發生的是事件捕獲,爲截獲事件提供了機會。而後是實際的目標接收到事件。最後一個階段是冒泡階段,能夠在這個階段對事件做出響應。

事件捕獲階段:在DOM事件流中,實際的目標(<div>
元素)在捕獲階段不會接收到事件。這意味着在捕獲階段,事件從document到<html>
再到<body>
後就中止了。
處於目標階段:事件在<div>
上發生,並在事件處理中被當作冒泡階段的一部分。
事件冒泡階段:冒泡階段發生,事件又傳播迴文檔。
雖然,「DOM2級事件」規範明確要求捕獲階段不會涉及事件目標,可是IE9及以上的現代瀏覽器都會在捕獲階段觸發事件目標對象上的事件。結果,就是有兩個機會在目標對象上面操做事件,也就是說上圖中的步驟4,既能夠在捕獲階段發生,也能夠在冒泡階段發生。
IE8級更早版本不支持DOM事件流,現代瀏覽器都支持DOM事件流。
事件流比較典型應用是事件委託。事件委託利用了事件冒泡,只指定一個事件處理程序,就能夠管理一類型的全部事件。
咱們查看一個經常使用例子,這是一個無序列表的DOM結構:
<ul> <li id="li1">我是第1個li</li> <li id="li2">我是第2個li</li> <li id="li3">我是第3個li</li> </ul>
咱們的需求是,點擊不一樣列,輸出不一樣的消息。
<li>
添加點擊事件,這樣能分別處理事件,展現不一樣的內容。document.getElementById('li1').addEventListener('click', function(e) { console.log('我是第一個li') }, false) document.getElementById('li2').addEventListener('click', function(e) { console.log('我是第2個li') }, false) document.getElementById('li3').addEventListener('click', function(e) { console.log('我是第3個li') }, false)
單擊每個<li>
,會輸出對應的內容。
<li>
元素的父元素<ul>
添加一個處理事件,document.querySelector('ul').addEventListener('click', function (e) { console.log(e.target.innerText) }, false)
單擊每個<li>
,會展現不一樣的<li>
中文本元素內容。
在這段代碼中,咱們只爲<ul>
元素添加了一個onclick事件處理程序。因爲全部<li>
都是<ul>
元素的子節點,並且它們的事件會冒泡,因此單擊事件最終會被這個函數處理。
以上兩種方式,第二種所具備的優點:
事前消耗更低。由於只取得了一個DOM元素,只添加了一個事件處理程序。
佔用的內存更少。每一個函數都是對象,都會佔用內存。
性能更優。 內存中的對象越多,性能就越差。
若是之後要增減<li>
元素,也不用修改事件方法,能夠獲取相同的處理結果。
因此,比較推薦使用第二種方式。
最適合採用事件委託技術的事件包括click
、mousedown
、mouseup
、keydown
、keyup
和keypress
。雖然mouseover
和mouseout
事件也冒泡,但要適當處理它們並不容易,並且常常要計算元素的位置。
參考資料:
JavaScript高級程序設計(第三版)- 第13章 事件
查看源碼:DOM2事件-捕獲-冒泡