一步,一步前進の一步
事件是文檔或者瀏覽器窗口中發生的一些交互瞬間。JS註冊事件處理程序來預訂事件,當事件發生的瞬間來執行相應的代碼,進而實現 JS 和 HTML(即文檔或者瀏覽器窗口) 的交互。javascript
事件流描述的是從頁面中接收事件的順序。html
用手指戳一下屏幕上的同心圓的中心,先點到的是最外圍的大圓,仍是最核心的小圓呢?這個就是事件流要處理的本質問題。早起的 IE 和 Netscape 對此有不一樣的觀點,IE認爲先點到的是最小的圓,而後在一層層的傳遞到最外面的大圓(事件冒泡),Netscape 正好相反,最早碰到的是最外圍的大圓,而後在一層層的追蹤到最精準的小圓(事件捕獲)。前端
事件冒泡:事件開始由最具體的元素接收,而後逐級向上傳播到較爲不具體的節點。java
事件捕獲:事件是從不太具體的節點開始產生接收,而最具體的節點應該是最後接收事件的。編程
事件流規範出來講,事件傳遞有三個階段: 捕獲階段、目標階段、冒泡階段。小程序
瀏覽器的事件處理大概就是註冊、監聽。程序開始就對將來會發生的某些事情,作出預期,對預期作出正確的反應。事件處理程序就是被註冊
的正確反應
,監聽
這個操做由瀏覽器本身完成。
瀏覽器提供了三種方法,爲事件綁定監聽函數。瀏覽器
<div onclick="doSomething()">
須要注意的是:此處的事件處理程序是須要帶小括號的,大概的過程是當 div 接收到事件時,會將onclick
後面的代碼原封不動的傳入JavaScript引擎執行,不加小括號就不會觸發處理程序。性能優化
此方式會讓 js 的代碼和 html 代碼雜糅在一塊兒,不易代碼的變動和維護,所以不推薦使用。dom
div.onclick = function (event) { console.log('觸發事件'); };
以元素節點對象的事件屬性的方式進行註冊,與第一種方式相似。
該事件只會在冒泡階段
觸發。編程語言
target.addEventListener(type, listener[, useCapture])
type
事件名稱,大小寫敏感;listener
處理函數;useCapture
是否在捕獲階段觸發。
推薦使用該方式進行事件的註冊,能夠對同一節點註冊多個事件處理函數。當前冒泡流是被大多瀏覽器支持,所以useCapture大多賦爲false。
document.addEventListener('click', hello, false); document.addEventListener('click', hello2, false);
早期的 IE 瀏覽器只支持冒泡流,有本身的事件註冊和移除的方法:attachEvent()
、detachEvent()
btn.attachEvent('onclick', function () { alert('ie browser'); });
須要注意的是處理函數的 this 是指向 window 的,而不像前兩種事件處理函數的 this 會指向事件所在的 dom 節點對象。
紅寶書方案代碼以下:
var EventUtil = { addHandler: function(elm, type, handler) { if (elm.addEventListener) { elm.addEventListener(type, handler, false); } else if (elm.attachEvent) { elm.attachEvent('on' + type, handler); } else { elm['on' + type] = handler; } } };
該方法是比較好的,可是處理函數執行時的 this 指向仍是有一點點問題,attachEvent
方式仍是指向 window 的,若是想更加完善,請參考 js 忍者祕籍上面的方案。
在處理事件時,咱們須要考慮一些性能的問題,有必要限制事件處理函數的數量,適當的時候將已有的事件處理程序移除掉或者採用事件委託機制減小注冊的個數。下面咱們簡單談談如何移除事件處理程序。
var EventUtil = { removeHandler: function (elm, type, handler) { if (elm.removeEventListener) { elm.removeEventListener(type, handler, false); } else if (elm.detachEvent) { elm.detachEvent('on' + type, handle); } else { elm['on' + type] = handler; } } }
事件的移除有個原則,怎麼註冊的就要原封不動的 copy 參數調用移除。此處須要注意的是當你的事件處理函數是匿名函數時,那將會永遠也清理不掉了。
處理函數執行時,this 指向現代的瀏覽器指向的是 事件所在dom 節點
,老 IE 指向window。
事件發生後,會產生一個事件對象,做爲參數傳給監聽函數。事件有若干的實例屬性和實例方法。只講下筆者認爲重要的currentTarget
、target
、preventDefault()
、.stopPropagation()
、stopImmediatePropagation()
。
currentTarget
事件處理函數註冊在什麼節點上,那麼 currentTarget 就永遠的指向了該節點。target
,咱們回顧一下事件流的概念,事件會經歷捕獲階段、目標階段、冒泡階段,能夠感受到事件的傳遞畫了個對稱的鉤,target 表示事件當前所處的節點位置。
咱們不只能夠讀取事件的狀態,還能夠人爲的改變它的內部狀態。
preventDefault()
阻止事件的默認行爲,如 a 標籤被點擊時就會跳轉到新的 url,咱們可使用event.preventDefault()來阻止跳轉。
stopPropagation()
阻斷事件的冒泡流或者事件捕獲流。但若是同一節點上註冊了多個事件處理程序,那麼該節點上的事件處理程序會繼續處理,它只會阻斷事件向上或向下的傳播。
stopImmediatePropagation()
更爲狠毒,事件會中止在該事件處理程序上,同節點的其餘事件處理程序也不會被觸發了。
因爲事件會在冒泡階段
向上傳播到父節點,所以能夠把子節點的監聽函數定義在父節點上,由父節點的監聽函數統一處理多個子元素的事件。這種方法叫作事件的代理(delegation)。
<ul> <li>test</li> <li>test</li> <li>test</li> <li>test</li> <li>test</li> ... </ul>
假設業務須要給 li 添加 click 監聽事件,那麼初學者可能會採用直接獲取 li 節點的方式進行事件程序的綁定,那麼有幾個 li,就須要註冊多少個事件處理程序。能夠採用事件代理機制實現性能優化,代碼以下:
var ul = document.querySelector('ul'); ul.addEventListener('click', function (event) { if (event.target.tagName.toLowerCase() === 'li') { // some code } });
事件代理和事件委託是同一個東西,主要是利用事件冒泡和事件的實例屬性 target,目的是減小事件處理程序的數量,提升性能。
紅寶書
阮一峯 事件模型
🌚 前端學習QQ羣: 538631558 🌚
【開發環境推薦】 Cloud Studio 是基於瀏覽器的集成式開發環境,支持絕大部分編程語言,包括 HTML五、PHP、Python、Java、Ruby、C/C++、.NET 小程序等等,無需下載安裝程序,一鍵切換開發環境。 Cloud Studio提供了完整的 Linux 環境,而且支持自定義域名指向,動態計算資源調整,能夠完成各類應用的開發編譯與部署。