JS中的事件順序(事件捕獲與冒泡)

問題

若是一個元素和它的祖先元素註冊了同一類型的事件函數(例如點擊等), 那麼當事件發生時事件函數調用的順序是什麼呢? 瀏覽器

好比, 考慮以下嵌套的元素:函數

-----------------------------------
| outer                           |
|   -------------------------     |
|   |inner                  |     |
|   -------------------------     |
|                                 |
-----------------------------------

兩個元素都有onclick的處理函數. 若是用戶點擊了inner, innerouter上的事件處理函數都會被調用. 但誰先誰後呢?this

兩個模型

在剛剛過去的那些糟糕年代, Netscape和M$對此有不一樣的見解. spa

Netscape認爲outer上的處理函數應該先被執行. 這被稱做event capturing. code

M$則認爲inner上的處理函數具備執行優先權. 這被叫作event bubbling. blog

兩種見解針鋒相對事件

事件捕獲(event capturing)

當使用事件捕獲時ip

| |
---------------| |-----------------
| outer        | |                |
|   -----------| |-----------     |
|   |inner     \ /          |     |
|   -------------------------     |
|        Event CAPTURING          |
-----------------------------------

outer上的事件處理器先觸發, 而後是inner上的element

事件冒泡(event bubbling)

/ \
---------------| |-----------------
| outer        | |                |
|   -----------| |-----------     |
|   |inner     | |          |     |
|   -------------------------     |
|        Event BUBBLING           |
-----------------------------------

與事件捕獲相反, 當使用事件冒泡時, inner上的事件處理器先被觸發, 其後是outer上面的開發

W3C 模型

W3C標準則取其折中方案. W3C事件模型中發生的任何事件, 先(從其祖先元素window)開始一路向下捕獲, 直到達到目標元素, 其後再次從目標元素開始冒泡.

1. 先從上往下捕獲
                  |
                 | |  / \
-----------------| |--| |-----------------
| outer          | |  | |                |
|   -------------| |--| |-----------     |
|   |   inner    \ /  | |          |     |
|   |                  |           |     |
|   |   2. 到達目標元素後從下往上冒泡|     |
|   --------------------------------     |
|        W3C event model                 |
------------------------------------------

而你做爲開發者, 能夠決定事件處理器是註冊在捕獲或者是冒泡階段. 若是addEventListener的最後一個參數是true, 那麼處理函數將在捕獲階段被觸發; 不然(false), 會在冒泡階段被觸發.

例如以下的代碼:

clipboard.png

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元素時, 以下事情發生了:

  1. 點擊事件開始於捕獲階段. 在此階段, 瀏覽器會在inner的全部祖先元素上查找點擊事件處理函數(從window開始).

  2. 結果找到了2個, 分別在documentouter上面, 並且這兩個事件處理函數的useCapture選項爲true, 說明它們是被註冊在捕獲階段的. 因而, documentouter的點擊處理函數被執行了.

  3. 繼續向下尋找, 直到達到inner元素自己. 捕獲階段就此結束. 此時進入冒泡階段, inner上的事件處理器獲得執行.

  4. 事件命中目標元素後開始向上冒泡, 一路查找是否有註冊了冒泡階段的祖先元素上的事件處理器. 因爲沒有找到, 所以什麼也沒發生.

最後的結果是:

clipboard.png

若是咱們把祖先元素的事件處理器註冊在冒泡階段的話(addEventListeneruseCapture選項爲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)

結果則是:

clipboard.png

傳統模型

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$模型的麻煩

在M$模型中, 沒有對e.currentTarget的支持, 更糟糕的是, this也不指向當前的HTML元素.

相關文章
相關標籤/搜索