若是在沒有敲代碼實際運行的狀況下,能確定且正確的回答如下問題,則能夠不繼續往下看。若是對某個題的回答存在疑慮或不清楚怎麼回事,能夠選擇繼續閱讀。javascript
<div id="div1" onclick="console.log(7)">
<div id="div2" onclick="console.log(6)"></div>
</div>
複製代碼
const div1 = document.getElementById('div1')
const div2 = document.getElementById('div2')
div2.onclick = function () { console.log(5) }
div1.addEventListener('click', function () { console.log(1) })
div2.addEventListener('click', function () { console.log(2) }, false)
div1.addEventListener('click', function () { console.log(3) }, { capture: true })
div2.addEventListener('click', function () { console.log(4) }, { capture: true })
複製代碼
若是繼續閱讀的話,上面的問題,會在正文中有分析。html
注:如無特殊說明,本文以chrome測試的結果爲準java
當在頁面上某個元素觸發特定事件時,頁面上哪些部分會觸發該事件?現代瀏覽器開發者設定,除了被點擊的目標元素,全部祖先元素都會觸發該事件,一直到window(現代瀏覽器,IE4和網景是document)。chrome
這樣又出現了新的問題,在window和目標元素都觸發事件,那是先在目標元素上觸發呢,仍是先在其餘元素上觸發呢?這就是事件流的概念。瀏覽器
事件流是事件在目標元素和祖先元素間的觸發順序,在早期,IE和網景實現了相反的事件流,IE4實現的是先觸發目標元素的事件,再向上一層一層觸發祖先元素的事件,到document對象(即事件冒泡);bash
但現代瀏覽器實現的事件流是DOM2級的事件標準,包含了IE、網景的實現,並且都把window也包含在內即,div -> body -> html -> document -> window
。函數
DOM2級事件流標準有三個階段:事件捕獲階段、出於目標階段、事件冒泡階段。先發生在事件捕獲階段,而後到目標元素,最後再冒泡上去到window。測試
既然在冒泡階段和捕獲階段都會觸發事件,那當添加了事件監聽方法以後,是否是在每一個元素上都每次事件都會觸發兩次呢?顯然不是,在DOM2中事件監聽機制提供了一個參數來決定事件是在捕獲階段生效仍是在冒泡階段生效,即addEventListenerui
// DOM2級事件監聽方法
// useCapture(可選):Boolean,表示 listener 會在該類型的事件捕獲階段傳播到該 EventTarget 時觸發。
// 默認爲false,即在冒泡階段觸發
target.addEventListener(type, listener[, useCapture]);
// 目前的事件監聽方法
// options(可選): 一個指定有關 listener 屬性的可選參數對象。可用的選項以下:
// capture: Boolean,表示 listener 會在該類型的事件捕獲階段傳播到該 EventTarget 時觸發。
// once: Boolean,表示 listener 在添加以後最多隻調用一次。若是是 true, listener 會在其被調用以後自動移除。
// passive: Boolean,表示 listener 永遠不會調用 preventDefault()。若是 listener 仍然調用了這個函數,客戶端將會忽略它並拋出一個控制檯警告。
target.addEventListener(type, listener[, options]);
複製代碼
在日常開發中,不多須要用到第三個參數,使用的是默認的false值,因此不少時候並無弄清楚,第三個參數有沒有、true、false有什麼區別。 能夠驗證事件流的過程:spa
<div id="div1"> <div id="div2"></div> </div>
複製代碼
const div1 = document.getElementById('div1')
const div2 = document.getElementById('div2')
// capture默認爲false,在事件流到達目標以後再網上傳遞,比目標元素上的事件觸發晚
div1.addEventListener('click', function () { console.log(1) })
div2.addEventListener('click', function () { console.log(2) }, false)
// capture爲true,在到達目標元素前的捕獲階段在div1上觸發,最早執行回調
div1.addEventListener('click', function () { console.log(3) }, { capture: true })
div2.addEventListener('click', function () { console.log(4) }, { capture: true })
複製代碼
div2是目標元素,在它上面capture爲true是否會先觸發? 其實當事件流處於於目標階段後,事件的回調函數會按照註冊的順序觸發,而無論capture是false仍是true,詳情參考連接。
因此上例的輸出順序就比較明顯了,3, 2, 4, 1
事件監聽的方式有三種:
經過HTML屬性的方式<div onclick="console.log('click')"></div>
DOM0中能夠經過js腳原本給指定元素提供事件處理函數,即
element.onclick = handlerhandler爲匿名函數或指定的函數名
複製代碼
在DOM2中,添加了新的事件監聽API,即addEventListener(type, handler[, options | useCapture])
,同時提供了取消監聽的removeEventListener(type, handler[, options | useCapture])
;顯然事件處理函數註冊後,要取消監聽,type/hanlder/useCapture的一致。
相比html屬性方式、DOM0級監聽方式,addEventListener的優點是什麼呢?主要有如下幾點:
addEventLinster可爲同一個事件註冊多個回調函數,以此觸發。而DOM0級註冊會覆蓋
addEventLinster能夠經過參數決定監聽是在冒泡階段生效,仍是在捕獲階段生效。element.onclick註冊的監聽只會在冒泡階段生效
更方便移除監聽
沿用前面的例子:
<div id="div1">
<div id="div2" onclick="console.log(7)"></div>
</div>
複製代碼
const div2 = document.getElementById('div2')
// 若是同時使用了HTML屬性方式,和DOM0方式,則DOM0方式會覆蓋HTML屬性方式
div2.onclick = function () { console.log(8) }
// 一樣這一個會覆蓋掉上一條,只會log出9
div2.onclick = function () { console.log(9) }
div2.addEventLisnter('click', function () { console.log(10) })
div2.addEventLisnter('click', function () { console.log(11) }) // 不會覆蓋,會log出9, 10, 11
複製代碼
DOM2能夠經過removeEventListener的方式移除處理函數,HTML屬性方式註冊和DOM0的監聽如何移除呢?
div2.onclick = null
// or
div2.setAttributer('onclick', false)
複製代碼
這兩個方法均可以把HTML屬性註冊或element.onclick方式註冊的監聽移除,但不會影響addEventListener註冊的監聽。
到這裏,最開始問題1中的輸出順序也容易了
<div id="div1" onclick="console.log(7)">
<div id="div2" onclick="console.log(6)"></div>
</div>
複製代碼
const div1 = document.getElementById('div1')
const div2 = document.getElementById('div2')
div2.onclick = function () { // 覆蓋掉html中的onclick屬性,6不會被輸出,同時在冒泡階段,這個方法最早被註冊
console.log(5)
}
div1.addEventListener('click', function () { console.log(1) })
div2.addEventListener('click', function () { console.log(2) }, false)
div1.addEventListener('click', function () { console.log(3) }, { capture: true })
div2.addEventListener('click', function () { console.log(4) }, { capture: true })
複製代碼
結果是: 3, 5, 2, 4, 7, 1,這裏須要注意的是5先於二、4被log出來,而7先於1被log出來(在chrome中的結果)
參考: