若是一個元素和它的祖先元素註冊了同一類型的事件函數(例如點擊等), 那麼當事件發生時事件函數調用的順序是什麼呢? 瀏覽器
好比, 考慮以下嵌套的元素:函數
----------------------------------- | outer | | ------------------------- | | |inner | | | ------------------------- | | | -----------------------------------
兩個元素都有onclick
的處理函數. 若是用戶點擊了inner
, inner
和outer
上的事件處理函數都會被調用. 但誰先誰後呢?this
在剛剛過去的那些糟糕年代, Netscape和M$對此有不一樣的見解. spa
Netscape認爲outer
上的處理函數應該先被執行. 這被稱做event capturing
. code
M$則認爲inner
上的處理函數具備執行優先權. 這被叫作event bubbling
. blog
兩種見解針鋒相對事件
當使用事件捕獲時ip
| | ---------------| |----------------- | outer | | | | -----------| |----------- | | |inner \ / | | | ------------------------- | | Event CAPTURING | -----------------------------------
outer
上的事件處理器先觸發, 而後是inner
上的element
/ \ ---------------| |----------------- | outer | | | | -----------| |----------- | | |inner | | | | | ------------------------- | | Event BUBBLING | -----------------------------------
與事件捕獲相反, 當使用事件冒泡時, inner上的事件處理器先被觸發, 其後是outer上面的開發
W3C標準則取其折中方案. W3C事件模型中發生的任何事件, 先(從其祖先元素window
)開始一路向下捕獲, 直到達到目標元素, 其後再次從目標元素開始冒泡.
1. 先從上往下捕獲 | | | / \ -----------------| |--| |----------------- | outer | | | | | | -------------| |--| |----------- | | | inner \ / | | | | | | | | | | | 2. 到達目標元素後從下往上冒泡| | | -------------------------------- | | W3C event model | ------------------------------------------
而你做爲開發者, 能夠決定事件處理器是註冊在捕獲或者是冒泡階段. 若是addEventListener
的最後一個參數是true
, 那麼處理函數將在捕獲階段被觸發; 不然(false), 會在冒泡階段被觸發.
例如以下的代碼:
var selector = document.querySelector.bind(document); selector('div.outer').addEventListener('click', (e) => { selector('p:first-of-type').textContent += 'outer clicked! ' }, true) selector('div.inner').addEventListener('click', (e) => { selector('p:first-of-type').textContent += 'inner clicked! ' }, false) document.addEventListener('click', (e) => { selector('p:first-of-type').textContent += 'document clicked! ' }, true)
當點擊inner
元素時, 以下事情發生了:
點擊事件開始於捕獲階段. 在此階段, 瀏覽器會在inner
的全部祖先元素上查找點擊事件處理函數(從window
開始).
結果找到了2個, 分別在document
和outer
上面, 並且這兩個事件處理函數的useCapture
選項爲true
, 說明它們是被註冊在捕獲階段的. 因而, document
和outer
的點擊處理函數被執行了.
繼續向下尋找, 直到達到inner
元素自己. 捕獲階段就此結束. 此時進入冒泡階段, inner
上的事件處理器獲得執行.
事件命中目標元素後開始向上冒泡, 一路查找是否有註冊了冒泡階段的祖先元素上的事件處理器. 因爲沒有找到, 所以什麼也沒發生.
最後的結果是:
若是咱們把祖先元素的事件處理器註冊在冒泡階段的話(addEventListener
的useCapture
選項爲false
):
var selector = document.querySelector.bind(document); selector('div.outer').addEventListener('click', (e) => { selector('p:first-of-type').textContent += 'outer clicked! ' console.log(e); }, false) selector('div.inner').addEventListener('click', (e) => { selector('p:first-of-type').textContent += 'inner clicked! ' console.log(e); }, false) document.addEventListener('click', (e) => { selector('p:first-of-type').textContent += 'document clicked! ' }, false)
結果則是:
element.onclick = function(){}
將被註冊在冒泡階段.
例如: 當點擊時的默認函數
若是在document
上註冊一個點擊函數:
document.addEventlistener('click', (e) => {}, false)
那麼任何元素上的點擊事件最後都會冒泡到這個事件處理器上並觸發函數 - 除非前面的事件處理函數阻止了冒泡(e.stopPropogation()
, 在這種狀況下事件不會繼續向上冒泡)
注意: e.stopPropagation()
只能阻止事件在冒泡階段的向上傳播. 若是被點擊元素的祖先元素有註冊在捕獲階段的事件處理器:
ancestorElem.addEventListner('click', (e) => { // do something... }, true)
那麼該祖先元素上的事件處理器照樣會在捕獲階段被觸發.
所以, 你能夠在document
上設置這麼一個處理函數, 當頁面上的任何元素被點擊時, 這個處理函數就被會觸發. 一個實用的例子就是下拉菜單: 當點擊文檔上除下拉菜單自己時任意一處時, 下拉菜單會被隱藏.
在冒泡或者捕獲階段, e.currentTarget
指向當前事件處理函數所附着的元素. 你也能夠用事件處理函數內的this
取而代之.
在M$模型中, 沒有對e.currentTarget
的支持, 更糟糕的是, this
也不指向當前的HTML元素.