在網頁中,若是想與使用者進行「互動」,必需要經過某種方法知道他都作了什麼。固然,瀏覽器開發者們早已根據 W3C 事件規範實現好了底層的邏輯,咱們只須要經過 Web API 中的 DOM Event,經過註冊想監聽的 DOM 元素和事件的事件監聽器(Event Listener)就能夠輕鬆掌握使用者在網頁上的一舉一動。javascript
咱們能夠在想要監聽事件的 DOM 元素上經過 addEventListener 註冊監聽器。例如:html
document.querySelector('#id').addEventListener('click', clickHandler)
當點擊 #id
元素時會觸發 clickHandler
並傳入一個事件,其內容包含事件傳遞過程當中必要的數據,例如目標元素、當前元素、傳遞階段等等。這時咱們即可以從中獲取所須要的數據,並針對這些數據作你想作的事。前端
如今的網站有大量的互動,若是經過事件監聽一個一個去寫,除了效能不好,寫起來也很麻煩;這時就體現出「事件代理」的重要性了!java
不過在說到事件代理以前,現須要理解 DOM Tree 上的時間傳遞機制是怎樣的react
能夠參考 W3C 所定義的 Event Flow 圖:git
規範中定義了時間傳遞的三個階段:程序員
如圖所示,當使用者觸發一個DOM 元素的事件時,首先會進入捕獲階段(Capture Phase),從根結點逐步向事件目標傳遞;到達目標後則進入目標階段(Target Phase),接着就開始折返,進入向根結點傳遞的冒泡階段(Bubbling Phase)。github
在使用 addEventListener
註冊事件監聽器時,能夠經過傳遞第三個參數,指定此事件監聽要在什麼階段觸發:面試
elem.addEventListener('click', eventHandler) // 未指定,預設爲冒泡 elem.addEventListener('click', eventHandler, false) // 冒泡 elem.addEventListener('click', eventHandler, true) // 捕獲 elem.addEventListener('click', eventHandler, { capture: true // 是否爲捕獲。 IE、Edge 不支持。其餘屬性請參考 MDN })
經過簡單的來回傳遞,這樣就能更精準的控制觸發的時機了!segmentfault
如今終於聊到了事件代理。因爲事件傳遞的機制,子元素的事件在傳遞過程當中勢必會通過它的父元素;而事件代理,顧名思義就是將子元素事件監聽器交由父元素代理。
什麼意思呢?咱們直接看個簡單的對照例子:
首先是 HTML 骨架:
<button id="push">push</button> <button id="pop">pop</button> <ul id="list"></ul>
沒有事件代理
(function() { document.querySelector('#push').addEventListener('click', pushHandler) document.querySelector('#pop').addEventListener('click', popHandler) const list = document.querySelector('#list') function pushHandler() { list.appendChild(getNewElem(list.childNodes.length)) } function popHandler() { document.querySelectorAll('#list>li')[list.childNodes.length - 1].remove() } function getNewElem(text) { const elem = document.createElement('li') elem.innerText = text elem.addEventListener('click', eventHandler) return elem } function eventHandler(e) { alert(e.target.innerText) } })()
有事件代理
(function() { document.querySelector('#push').addEventListener('click', pushHandler) document.querySelector('#pop').addEventListener('click', popHandler) const list = document.querySelector('#list') list.addEventListener('click', listClickHandler) function pushHandler() { list.appendChild(getNewElem(list.childNodes.length)) } function popHandler() { document.querySelectorAll('#list>li')[list.childNodes.length - 1].remove() } function getNewElem(text) { const elem = document.createElement('li') elem.innerText = text return elem } function listClickHandler(e){ if (e.target.tagName === 'LI') alert(e.target.innerText) } })()
差別在於事件監聽的目標元素
在沒有事件代理的版本中每個 li
上都註冊了事件監聽器,當數量愈來愈多時瀏覽器也就創建了愈來愈多的監聽器,無形中對性能有很大的影響;反之在有事件代理的版本中,將事件監聽器註冊在了外層的 ul
上,不管內容有多少,瀏覽器都只須要承擔一組事件監聽器的消耗。
在 DOM 事件處理的這部分,jQuery 和 Vue 都將原生的事件監聽器作了封裝,方便咱們快速設定、使用,甚至會自動幫你移除無用的事件監聽。
可是在 React 中,React DOM 上直接註冊的事件監聽器,其實監聽的是 React 額外封裝過的 React DOM Event,並將所有事件代理到 document 上,這與原生事件有很大不一樣;特別是若是混用 React DOM Even tListener 及原生的 addEventListener
,事件監聽器之間的執行順序頗有可能會和預期不一致,在寫 React 的時候要特別注意。
有興趣深刻研究的話能夠在React 源碼 中查找關於事件處理的代碼部分。