系統(tǒng)理解瀏覽器之事件機(jī)制
事件流
在早期 IE 和 Netscape 團(tuán)隊(duì)在開(kāi)發(fā)第四代瀏覽器的時(shí)候,遇到一個(gè)問(wèn)題:當(dāng)點(diǎn)擊一個(gè)按鈕的時(shí)候,是應(yīng)該先處理父級(jí)的事件呢?還是應(yīng)該先處理按鈕的事件呢?IE 和 Netscape 給出了 2 種完全相反的答案,IE 提出事件冒泡的概念,而 Netscape 則支持事件捕獲。
事件冒泡
事件冒泡認(rèn)為事件應(yīng)該由最具體的元素開(kāi)始觸發(fā),然后層層往父級(jí)傳播:
事件捕獲
而事件捕獲則相反,認(rèn)為最外層的元素應(yīng)該最先收到事件,然后層層往下級(jí)傳遞:

DOM 事件流
為了在瀏覽器中兼容這 2 種事件流,在 DOM2 Events 規(guī)范中將事件流分為 3 個(gè)階段:事件捕獲階段、到底目標(biāo)階段、事件冒泡階段。
可以通過(guò)指定 addEventListener 的第三個(gè)參數(shù)為 true 來(lái)設(shè)置事件是在捕獲階段調(diào)用事件處理程序,默認(rèn)是 false 指在冒泡階段調(diào)用事件處理程序。
- 所有現(xiàn)代瀏覽器都支持 DOM 事件流,只有 IE8 及更早版本不支持。
 
事件處理程序
HTML 事件處理程序
就是將事件處理程序直接綁定到 HTML 的屬性中:
- // 方式一
 - <div onclick="console.log('hello world')"></div>
 - 方式二
 - <div onclick="print(event)"></div>
 - <script>
 - function print(e) { }
 - </script>
 
HTML 事件處理程序修改事件相對(duì)麻煩,可能需要同時(shí)修改 HTML 和 JS,所以大家都不愛(ài)使用這種方式綁定事件。
DOM0 事件處理程序
將一個(gè)函數(shù)賦值給 DOM 元素的一個(gè)事件處理程序?qū)傩裕热?onclick:
- let btn = document.getElementById('div')
 - // 添加事件
 - btn.onclick = function() { }
 - // 移除事件
 - btn.onclick = null
 
DOM2 事件處理程序
通過(guò) addEventListener 可以添加 DOM2 級(jí)別的事件處理程序,它接收 3 個(gè)參數(shù):事件名、事件處理程序和 useCapture (它是一個(gè)可選參數(shù),是個(gè)布爾值,默認(rèn)為 false 表示在冒泡階段調(diào)用事件處理程序)
- let btn = document.getElementById('div')
 - btn.addEventListener('click', () => {
 - }, false)
 
和 DOM0 事件處理程序的區(qū)別:
- addEventListener 可以改變事件流,即可以在捕獲階段觸發(fā)事件,而 DOM0 是不行的;
 - addEventListener 可以為同一個(gè)元素多次添加同一類(lèi)型的事件處理程序,先添加的事件處理程序會(huì)先觸發(fā),而 DOM0 如果給同一個(gè)元素綁定多個(gè)相同類(lèi)型的事件處理程序的話(huà),則后面添加的會(huì)覆蓋前面定義的;
 
它有幾個(gè)注意事項(xiàng):
- 如果不需要在捕獲階段進(jìn)行攔截操作,則 useCapture 即第三個(gè)參可以不傳;
 - 通過(guò) addEventListener 添加的事件處理程序只能通過(guò) removeEventListener 移除,而且綁定的事件處理程序必須是同一個(gè)。
 
- let btn = document.getElementById('div')
 - let handler = function() { }
 - btn.addEventListener("click", handler)
 - btn.removeEventListener("click", handler)
 
事件處理函數(shù)
由于 addEventListener 無(wú)法兼容 IE8 及更早版本,所以此時(shí)就可以使用 attachEvent 添加事件處理程序和用 detachEvent 移除事件處理程序。
- let btn = document.getElementById('div')
 - btn.attachEvent("onclick", function() { })
 
它有這么幾個(gè)注意事項(xiàng):
- 注冊(cè)的事件名和 DOM0 一樣,需要帶上 on,比如 onclick;
 - 在通過(guò) attachEvent 添加的事件處理程序內(nèi)部 this 會(huì)指向 window,而 DOM0 和 DOM2的 this 會(huì)指向元素本身;
 - 和 addEventListener 一樣, attachEvent 也可以針對(duì)同一元素多次添加同一個(gè)事件類(lèi)型的處理程序,但是觸發(fā)順序是后定義的先觸發(fā);
 - 通過(guò) detachEvent 移除事件處理程序的時(shí)候,處理函數(shù)必須是和注冊(cè)的同一個(gè),這點(diǎn)和 addEventListener 保持一致;
 
attachEvent 和 detachEvent 是 IE 專(zhuān)屬的 API,所以如果有兼容性要求,我們可以寫(xiě)出跨瀏覽器的事件處理程序:
- var EventUtil = {
 - addHandler: function(element, type, handler) {
 - if (element.addEventListener) {
 - element.addEventListener(type, handler, false)
 - } else if (element.attachEvent) {
 - element.attachEvent("on" + type, handler)
 - } else {
 - element["on" + type] = handler;
 - }
 - },
 - removeHandler: function(element, type, handler) {
 - if (element.removeEventListener) {
 - element.removeEventListener(type, handler, false)
 - } else if (element.detachEvent) {
 - element.detachEvent("on" + type, handler)
 - } else {
 - element["on" + type] = null
 - }
 - }
 - }
 
事件對(duì)象
通過(guò)不同的事件處理程序添加的事件,event 對(duì)象的屬性略有不同,我們不需要記住他們的差異,只需要在平時(shí)寫(xiě)代碼的時(shí)候養(yǎng)成一個(gè)寫(xiě)兼容代碼的習(xí)慣即可,如下是一個(gè)兼容各種 event 對(duì)象的事件處理程序:
- let handler = function(event) {
 - // 事件對(duì)象
 - let event = event || window.event
 - // 目標(biāo)元素
 - let target = event.target || event.srcElement
 - // 阻止默認(rèn)事件觸發(fā)
 - if (event.preventDefault) {
 - event.preventDefault()
 - } else {
 - event.returnValue = false
 - }
 - // 阻止事件冒泡
 - if (event.stopPropagation) {
 - event.stopPropagation()
 - } else {
 - event.cancelBubble = true
 - }
 - }
 
事件類(lèi)型
DOM3 Events 定義了如下事件類(lèi)型:
- 用戶(hù)界面事件(UIEvent):涉及與 BOM 交互的通用瀏覽器事件,比如 onload、resize、scroll、input、select 等;
 - 焦點(diǎn)事件(FocusEvent):在元素獲得和失去焦點(diǎn)時(shí)觸發(fā),比如 focus、blur;
 - 鼠標(biāo)事件(MouseEvent):使用鼠標(biāo)在頁(yè)面上執(zhí)行某些操作時(shí)觸發(fā),比如 click、mousedown、mouseover 等;
 - 滾輪事件(WheelEvent):使用鼠標(biāo)滾輪(或類(lèi)似設(shè)備)時(shí)觸發(fā),比如 mousewheel;
 - 輸入事件(InputEvent):向文檔中輸入文本時(shí)觸發(fā),比如 textInput;
 - 鍵盤(pán)事件(KeyboardEvent):使用鍵盤(pán)在頁(yè)面上執(zhí)行某些操作時(shí)觸發(fā),比如 keydown、keypress;
 - 合成事件(CompositionEvent):在使用某種 IME(Input Method Editor,輸入法編輯器)輸入字符時(shí)觸發(fā),比如 compositionstart。
 
事件委托
事件委托是指將多個(gè)元素上綁定的事件通過(guò)利用事件冒泡的原理從而轉(zhuǎn)移到他們共同的父級(jí)上去綁定,從而在一定程度上起到性能優(yōu)化的作用,有的人也喜歡叫它事件代理。比如在 Vue中經(jīng)常會(huì)將事件綁定到每個(gè)列表項(xiàng)中:
- <ul>
 - <li v-for="item in list" :key="item" @click="handleClick(item)">{{item}}</li>
 - </ul>
 
其實(shí)更好的做法是利用事件委托,將事件綁定到 ul 上:
- <ul @click="handleClick">
 - <li v-for="item in list" :key="item" :data-item="item">{{item}}</li>
 - </ul>
 
- handleClick(event) {
 - let target = event.target
 - if (target === 'li') {
 - let data = target.dataset.item
 - }
 - }
 
感謝閱讀首先感謝你閱讀本文,相信你付出的時(shí)間值得擁有這份回報(bào)。


















 
 
 






 
 
 
 