[TOC]
# 事件流

事件流包括三個階段:事件捕獲階段、處于目標階段和事件冒泡階段
# 事件處理程序
click、mousemove、load 等都是事件的名字,響應某個事件的函數就叫事件處理程序(或事件偵聽器),事件處理程序以 "on" 開頭,因此,click 事件的事件處理程序就是 onclick,為事件指定事件處理程序的方式有多種。
## 1、通過 HTML 指定事件處理程序
```html
<input type="button" value="Click Me" onclick="alert('Clicked')" />
```
```html
<script type="text/javascript">
function showMessage(){
alert("Hello world!");
}
</script>
<input type="button" value="Click Me" onclick="showMessage()" />
```
這樣指定事件處理程序有一些獨到之處:
- 函數中有一個局部變量 event,即事件對象
- 在這個函數內部,this 值等于事件的目標元素,例如:
```html
<!-- 輸出 "Click Me" -->
<input type="button" value="Click Me" onclick="alert(this.value)">
```
缺點:
- HTML 與 JavaScript 代碼緊密耦合。如果要更換事件處理程序,就要改動兩個地方:HTML 代碼和 JavaScript 代碼
## 2、DOM0 級事件處理程序
每個元素(包括 window 和 document)都有自己的事件處理程序屬性,這些屬性通常全部小寫, 例如 onclick。將這種屬性的值設置為一個函數,就可以指定事件處理程序,如下所示:
```
var btn = document.getElementById("myBtn")
btn.onclick = function () {
alert("Clicked")
}
```
- 以這種方式添加的事件處理程序會在事件流的冒泡階段被處理
- 使用 DOM0 級方法指定的事件處理程序被認為是元素的方法,this 指向的是被設置事件處理程序的元素
- 可以通過 `btn.onclick = null` 的形式刪除以該方法設置的事件處理程序
## 3、DOM2 級事件處理程序
"DOM2級事件" 定義了兩個方法,用于處理指定和刪除事件處理程序的操作:`addEventListener()`和 `removeEventListener()`。所有 DOM 節點中都包含這兩個方法,并且它們都接受 3 個參數:要處理的事件名、作為事件處理程序的函數和一個布爾值。最后這個布爾值參數如果是 true,表示在捕獲階段調用事件處理程序;如果是 false,表示在冒泡階段調用事件處理程序
```
var btn = document.getElementById("myBtn")
btn.addEventListener("click", function () {
alert(this.id) // 同樣的,該函數被視為元素節點的方法
}, false)
```
- 使用 DOM2 級方法可以添加多個事件處理程序,按照添加順序觸發
- 通過`addEventListener()`添加的事件處理程序只能使用 `removeEventListener()`來移除,需要注意的是,使用匿名函數是無法刪除的
```
var btn = document.getElementById("myBtn");
btn.addEventListener("click", function(){
alert(this.id);
}, false);
//這里省略了其他代碼
btn.removeEventListener("click", function(){ //沒有用!
alert(this.id);
}, false);
```
傳入的第二個參數必須是同一個函數,函數是引用類型,即所指內存地址相同
```
var btn = document.getElementById("myBtn");
var handler = function(){
alert(this.id);
};
btn.addEventListener("click", handler, false);
//這里省略了其他代碼
btn.removeEventListener("click", handler, false); //有效!
```
- 大多數情況下,都是將事件處理程序添加到事件流的冒泡階段,這樣可以最大程度地兼容各種瀏覽器
## 4、兼容 IE 早期版本
IE 實現了與 DOM 中類似的兩個方法:`attachEvent()`和`detachEvent()`。這兩個方法接受相同的兩個參數:事件處理程序名稱與事件處理程序函數。由于 IE8 及更早版本只支持事件冒泡,所以通過`attachEvent()`添加的事件處理程序都會被添加到冒泡階段
# 事件對象
event 對象包含與創建它的特定事件有關的屬性和方法。觸發的事件類型不一樣,可用的屬性和方法也不一樣。下面列出**常用的**所有事件的 event 對象都具有的成員和方法。
|屬性/方法 |類型 |說明|
| -- | -- | -- |
|bubbles |Boolean |表明事件是否冒泡|
|cancelable| Boolean | 表明是否可以取消事件的默認行為|
|currentTarget| Element |指向注冊該事件處理程序的元素|
|target |Element| 指向觸發該事件的元素|
|defaultPrevented| Boolean | 為 true 表示已經調用了 preventDefault()|
|eventPhase |Integer |調用事件處理程序的階段:1 表示捕獲階段,2 表示"處于目標",3 表示冒泡階段|
|preventDefault() |Function |取消事件的默認行為。如果 cancelable 是 true,則可以使用這個方法
|stopImmediatePropagation()| Function | 取消事件的進一步捕獲或冒泡,同時阻止任何事件處理程序被調用|
|stopPropagation() |Function | 取消事件的進一步捕獲或冒泡。如果 bubbles 為 true,則可以使用這個方法|
|type |String| 被觸發的事件的類型|
當需要通過一個函數處理多個事件時,可以使用 type 屬性
```
var btn = document.getElementById('myBtn')
var handler = function (event) {
switch(event.type) {
case 'click':
alert('Clicked')
break
case 'mouseover':
event.target.style.backgroundColor = 'red'
break
case 'mouseout':
event.target.style.backgroundColor = ''
break
}
}
btn.onclick = handler
btn.onmouseover = handler
btn.onmouseout = handler
```
要阻止特定事件的默認行為,可以使用 `preventDefault()`方法。例如,鏈接的默認行為就是在被單擊時會導航到其 href 特性指定的 URL。如果你想阻止鏈接導航這一默認行為,那么通過鏈接的 onclick 事件處理程序可以取消它,如下面的例子所示
```
var link = document.getElementById('myLink')
link.onclick = function (event) {
event.preventDefault()
}
```
[target 與 currentTarget](https://www.cnblogs.com/yewenxiang/p/6171411.html)
# 事件類型
常見的事件類型大致有以下幾類:
- UI(User Interface,用戶界面)事件,當用戶與頁面上的元素交互時觸發;
- 焦點事件,當元素獲得或失去焦點時觸發;
- 鼠標事件,當用戶通過鼠標在頁面上執行操作時觸發;
- 滾輪事件,當使用鼠標滾輪(或類似設備)時觸發;
- 文本事件,當在文檔中輸入文本時觸發;
- 鍵盤事件,當用戶通過鍵盤在頁面上執行操作時觸發;
- 變動(mutation)事件,當底層 DOM 結構發生變化時觸發。
## UI 事件
- load:當頁面完全加載后在 window 上面觸發,當所有框架都加載完畢時在框架集上面觸發,當圖像加載完畢時在`<img>`元素上面觸發,或者當嵌入的內容加載完畢時在`<object>`元素上面觸發。
- unload:當頁面完全卸載后在 window 上面觸發,當所有框架都卸載后在框架集上面觸發,或者當嵌入的內容卸載完畢后在`<object>`元素上面觸發。
- abort:在用戶停止下載過程時,如果嵌入的內容沒有加載完,則在`<object>`元素上面觸發。
- error:當發生 JavaScript 錯誤時在 window 上面觸發,當無法加載圖像時在`<img>`元素上面觸發,當無法加載嵌入內容時在`<object>`元素上面觸發,或者當有一或多個框架無法加載時在框架集上面觸發。
- select:當用戶選擇文本框(`<input>`或`<texterea>`)中的一或多個字符時觸發。
- resize:當窗口或框架的大小變化時在 window 或框架上面觸發。
- scroll:當用戶滾動帶滾動條的元素中的內容時,在該元素上面觸發。<body>元素中包含所加載頁面的滾動條。
## 焦點事件
記住 blur 和 focus 即可
- blur:在元素失去焦點時觸發。這個事件不會冒泡;所有瀏覽器都支持它
- focus:在元素獲得焦點時觸發。這個事件不會冒泡;所有瀏覽器都支持它。
## 鼠標和滾輪事件
- click:在用戶單擊主鼠標按鈕(一般是左邊的按鈕)或者按下回車鍵時觸發。這一點對確保易訪問性很重要,意味著 onclick 事件處理程序既可以通過鍵盤也可以通過鼠標執行。
- dblclick:在用戶雙擊主鼠標按鈕(一般是左邊的按鈕)時觸發。
- mousedown:在用戶按下了任意鼠標按鈕時觸發。不能通過鍵盤觸發這個事件。
- mouseenter:在鼠標光標從元素外部首次移動到元素范圍之內時觸發。這個事件不冒泡,而且在光標移動到后代元素上不會觸發。
- mouseleave:在位于元素上方的鼠標光標移動到元素范圍之外時觸發。這個事件不冒泡,而且在光標移動到后代元素上不會觸發。
- mousemove:當鼠標指針在元素內部移動時重復地觸發。不能通過鍵盤觸發這個事件
- mouseout:在鼠標指針位于一個元素上方,然后用戶將其移入另一個元素時觸發。又移入的另一個元素可能位于前一個元素的外部,也可能是這個元素的子元素。不能通過鍵盤觸發這個事件。
- mouseover:在鼠標指針位于一個元素外部,然后用戶將其首次移入另一個元素邊界之內時觸發。不能通過鍵盤觸發這個事件。
- mouseup:在用戶釋放鼠標按鈕時觸發。不能通過鍵盤觸發這個事件
### 鼠標事件中的位置信息:
1.客戶區坐標位置 clientX、clientY,表示事件發生時鼠標指針在視口中的水平位置和垂直位置,如圖

2.頁面坐標位置 pageX、pageY,這兩個屬性表示鼠標光標在頁面中的位置,因此坐標是從頁面本身而非視口的左邊和頂邊計算的,在頁面沒有滾動的情況下,pageX 和 pageY 的值與 clientX 和 clientY 的值相等
3.屏幕坐標位置 screenX、screenY,鼠標事件發生時,不僅會有相對于瀏覽器窗口的位置,還有一個相對于整個電腦屏幕的位置。通過 screenX 和 screenY 屬性就可以確定鼠標事件發生時鼠標指針相對于整個屏幕的坐標信息

### 修改鍵
雖然鼠標事件主要是使用鼠標來觸發的,但在按下鼠標時鍵盤上的某些鍵的狀態也可以影響到所要采取的操作。這些修改鍵就是 Shift、Ctrl、Alt 和 Meta(在 Windows 鍵盤中是 Windows 鍵,在蘋果機中 是 Cmd 鍵),它們經常被用來修改鼠標事件的行為。DOM 為此規定了 4 個屬性,表示這些修改鍵的狀態:`shiftKey`、`ctrlKey`、`altKey` 和 `metaKey`。這些屬性中包含的都是布爾值,如果相應的鍵被按下了,則值為 true,否則值為 false。當某個鼠標事件發生時,通過檢測這幾個屬性就可以確定用戶是否同時按下了其中的鍵。
### 相關元素
DOM 通過 event 對象的 `relatedTarget` 屬性提供了相關元素的信息。這個屬性只對于 mouseover 和 mouseout 事件才包含值;對于其他事件,這個屬性的值是 null。
對 mouseover 事件而言,事件的主目標是獲得光標的元素,而相關元素就是那個失去光標的元素。類似地,對 mouseout 事件而言,事件的主目標是失去光標的元素,而相關元素則是獲得光標的元素。
### 鼠標滾輪事件
與 mousewheel 事件對應的 event 對象除包含鼠標事件的所有標準信息外, 還包含一個特殊的 `wheelDelta` 屬性。當用戶向前滾動鼠標滾輪時,wheelDelta 是 120 的倍數;當用 戶向后滾動鼠標滾輪時,wheelDelta 是 120 的倍數。

多數情況下,只要知道鼠標滾輪滾動的方向就夠了,而這通過檢測 wheelDelta 的正負號就可以確定
### 觸摸設備與鼠標事件
iOS 和 Android 設備的實現非常特別,因為這些設備沒有鼠標。開發時,要記住以下幾點。
- 不支持 dblclick 事件。雙擊瀏覽器窗口會放大畫面,而且沒有辦法改變該行為。
- 輕擊可單擊元素會觸發 mousemove 事件。如果此操作會導致內容變化,將不再有其他事件發生;如果屏幕沒有因此變化,那么會依次發生 mousedown、mouseup 和 click 事件。輕擊不可單擊的元素不會觸發任何事件。可單擊的元素是指那些單擊可產生默認操作的元素(如鏈接),或者那些已經被指定了 onclick 事件處理程序的元素。
- mousemove 事件也會觸發 mouseover 和 mouseout 事件。
- 兩個手指放在屏幕上且頁面隨手指移動而滾動時會觸發 mousewheel 和 scroll 事件。
## 鍵盤與文本事件
有 3 個鍵盤事件,簡述如下。
- keydown:當用戶按下鍵盤上的任意鍵時觸發,而且如果按住不放的話,會重復觸發此事件。
- keypress:當用戶按下鍵盤上的字符鍵時觸發,而且如果按住不放的話,會重復觸發此事件。按下 Esc 鍵也會觸發這個事件。
- keyup:當用戶釋放鍵盤上的鍵時觸發。
在用戶按了一下鍵盤上的字符鍵時,首先會觸發 keydown 事件,然后緊跟著是 keypress 事件,最后會觸發 keyup 事件。其中,keydown 和 keypress 都是在文本框發生變化之前被觸發的;而 keyup 事件則是在文本框已經發生變化之后被觸發的。如果用戶按下了一個字符鍵不放,就會重復觸發 keydown 和 keypress 事件,直到用戶松開該鍵為止。
**鍵碼**
在發生 keydown 和 keyup 事件時,event 對象的 `keyCode` 屬性中會包含一個代碼,與鍵盤上一個特定的鍵對應。對數字字母字符鍵,keyCode 屬性的值與 ASCII 碼中對應小寫字母或數字的編碼相同。因此,數字鍵 7 的 keyCode 值為 55,而字母 A 鍵的 keyCode 值為 65。
常用鍵及鍵碼:
| 鍵 | 鍵碼 | 鍵 | 鍵碼 |
| --- | --- | --- | --- |
| 退格 (Backspace) | 8 | 制表 (Tab) | 9 |
| 回車 (Enter)| 13| 上檔 (Shift)| 16 |
|控制 (Ctrl) | 17| Alt | 18|
| 暫停/中斷 (Pause/Break)|19 |大寫鎖定 (Caps Lock) |20 |
|數字小鍵盤1~9 |97~105 | 退出 (Esc)| 27|
| 左箭頭 (Left Arrow)|37 |上箭頭 (Up Arrow) |38 |
| 右箭頭 (Right Arrow)| 39| 下箭頭 (Down Arrow)| 40 |
也可以使用 `key` 和 `char` 屬性來判斷按鍵:
key 屬性是為了取代 keyCode 而新增的,它的值是一個字符串。在按下某個字符鍵時,key 的值就是相應的文本字符(如“k”或“M”);在按下非字符鍵時,key 的值是相應鍵的名(如“Shift” 或“Down”)。而 char 屬性在按下字符鍵時的行為與 key 相同,但在按下非字符鍵時值為 null。
> IE9 支持 key 屬性,但不支持 char 屬性;這兩個屬性存在跨瀏覽器問題
# HTML5 事件
- DOMContentLoaded 事件:DOMContentLoaded 事件在形成完整的 DOM 樹之后就會觸發, 不理會圖像、JavaScript 文件、CSS 文件或其他資源是否已經下載完畢。與 load 事件不同, DOMContentLoaded 支持在頁面下載的早期添加事件處理程序,這也就意味著用戶能夠盡早地與頁面進行交互。
- hashchange 事件:HTML5 新增了 hashchange 事件,以便在 URL 的參數列表(及 URL 中“#”號后面的所有字符串) 發生變化時通知開發人員。之所以新增這個事件,是因為在 Ajax 應用中,開發人員經常要利用 URL 參數列表來保存狀態或導航信息。
必須要把 hashchange 事件處理程序添加給 window 對象,然后 URL 參數列表只要變化就會調用 它。此時的 event 對象應該額外包含兩個屬性:`oldURL` 和 `newURL`。這兩個屬性分別保存著參數列表變化前后的完整 URL。例如:
```
window.addEventListener('hashchange', function (event) {
console.log(`old URL: ${event.oldURL} \n new URL: ${event.newURL}`)
})
```
# 觸摸與手勢事件
## 觸摸事件
- touchstart:當手指觸摸屏幕時觸發;即使已經有一個手指放在了屏幕上也會觸發。
- touchmove:當手指在屏幕上滑動時連續地觸發。在這個事件發生期間,調用 preventDefault() 可以阻止滾動。
- touchend:當手指從屏幕上移開時觸發。
除了常見的 DOM 屬性外,觸摸事件還包含下列三個用于跟蹤觸摸的屬性。
- touches:表示當前跟蹤的觸摸操作的 Touch 對象的數組。
- targetTouchs:特定于事件目標的 Touch 對象的數組。
- changeTouches:表示自上次觸摸以來發生了什么改變的 Touch 對象的數組。
## 手勢事件
當兩個手指觸摸屏幕時就會產生手勢,手勢通常會改變顯示項的大小,或者旋轉顯示項。有三個手勢事件,分別介紹如下。
- gesturestart:當一個手指已經按在屏幕上而另一個手指又觸摸屏幕時觸發。
- gesturechange:當觸摸屏幕的任何一個手指的位置發生變化時觸發。
- gestureend:當任何一個手指從屏幕上面移開時觸發
只有兩個手指都觸摸到事件的接收容器時才會觸發這些事件。在一個元素上設置事件處理程序,意味著兩個手指必須同時位于該元素的范圍之內,才能觸發手勢事件(這個元素就是目標)。由于這些事件冒泡,所以將事件處理程序放在文檔上也可以處理所有手勢事件。此時,事件的目標就是兩個手指都位于其范圍內的那個元素。
觸摸事件和手勢事件之間存在某種關系。當一個手指放在屏幕上時,會觸發 touchstart 事件。如果另一個手指又放在了屏幕上,則會先觸發 gesturestart 事件,隨后觸發基于該手指的 touchstart 事件。如果一個或兩個手指在屏幕上滑動,將會觸發 gesturechange 事件。但只要有一個手指移開, 就會觸發 gestureend 事件,緊接著又會觸發基于該手指的 touchend 事件
# 事件委托
對“事件處理程序過多”問題的解決方案就是事件委托。事件委托利用了事件冒泡,只指定一個事件處理程序,就可以管理某一類型的所有事件。例如,click 事件會一直冒泡到 document 層次。也就是說,我們可以為整個頁面指定一個 onclick 事件處理程序,而不必給每個可單擊的元素分別添加事件處理程序。以下面的 HTML 代碼為例。
```html
<ul id="myLinks">
<li id="goSomewhere">Go somewhere</li>
<li id="doSomething">Do something</li>
<li id="sayHi">Say hi</li>
</ul>
```
按照傳統的做法,需要像下面這樣為它們添加 3 個事 件處理程序。
```js
var item1 = document.getElementById("goSomewhere")
var item2 = document.getElementById("doSomething")
var item3 = document.getElementById("sayHi")
item1.addEventListener('click', function (event) {
location.href = 'http://www.wrox.com'
})
item2.addEventListener('click', function (event) {
document.title = `I changed the documetns title`
})
item3.addEventListener('click', function (event) {
alert('hi')
})
```
如果在一個復雜的 Web 應用程序中,對所有可單擊的元素都采用這種方式,那么結果就會有數不清的代碼用于添加事件處理程序。此時,可以利用事件委托技術解決這個問題。使用事件委托,只需在 DOM 樹中盡量最高的層次上添加一個事件處理程序,如下面的例子所示。
```
var list = document.getElementById('myLinks')
list.addEventListener('click', function (event) {
event = event ? event : window.event // 兼容
var target = event.target // 觸發該事件的元素
switch(target.id) {
case 'doSomething':
document.title = `I changed the document's title`
break
case 'goSomewhere':
location.href = 'http://www.wrox.com'
break
case 'sayHi':
alert('Hi')
break
}
})
```
- 序言 & 更新日志
- H5
- Canvas
- 序言
- Part1-直線、矩形、多邊形
- Part2-曲線圖形
- Part3-線條操作
- Part4-文本操作
- Part5-圖像操作
- Part6-變形操作
- Part7-像素操作
- Part8-漸變與陰影
- Part9-路徑與狀態
- Part10-物理動畫
- Part11-邊界檢測
- Part12-碰撞檢測
- Part13-用戶交互
- Part14-高級動畫
- CSS
- SCSS
- codePen
- 速查表
- 面試題
- 《CSS Secrets》
- SVG
- 移動端適配
- 濾鏡(filter)的使用
- JS
- 基礎概念
- 作用域、作用域鏈、閉包
- this
- 原型與繼承
- 數組、字符串、Map、Set方法整理
- 垃圾回收機制
- DOM
- BOM
- 事件循環
- 嚴格模式
- 正則表達式
- ES6部分
- 設計模式
- AJAX
- 模塊化
- 讀冴羽博客筆記
- 第一部分總結-深入JS系列
- 第二部分總結-專題系列
- 第三部分總結-ES6系列
- 網絡請求中的數據類型
- 事件
- 表單
- 函數式編程
- Tips
- JS-Coding
- Framework
- Vue
- 書寫規范
- 基礎
- vue-router & vuex
- 深入淺出 Vue
- 響應式原理及其他
- new Vue 發生了什么
- 組件化
- 編譯流程
- Vue Router
- Vuex
- 前端路由的簡單實現
- React
- 基礎
- 書寫規范
- Redux & react-router
- immutable.js
- CSS 管理
- React 16新特性-Fiber 與 Hook
- 《深入淺出React和Redux》筆記
- 前半部分
- 后半部分
- react-transition-group
- Vue 與 React 的對比
- 工程化與架構
- Hybird
- React Native
- 新手上路
- 內置組件
- 常用插件
- 問題記錄
- Echarts
- 基礎
- Electron
- 序言
- 配置 Electron 開發環境 & 基礎概念
- React + TypeScript 仿 Antd
- TypeScript 基礎
- React + ts
- 樣式設計
- 組件測試
- 圖標解決方案
- Storybook 的使用
- Input 組件
- 在線 mock server
- 打包與發布
- Algorithm
- 排序算法及常見問題
- 劍指 offer
- 動態規劃
- DataStruct
- 概述
- 樹
- 鏈表
- Network
- Performance
- Webpack
- PWA
- Browser
- Safety
- 微信小程序
- mpvue 課程實戰記錄
- 服務器
- 操作系統基礎知識
- Linux
- Nginx
- redis
- node.js
- 基礎及原生模塊
- express框架
- node.js操作數據庫
- 《深入淺出 node.js》筆記
- 前半部分
- 后半部分
- 數據庫
- SQL
- 面試題收集
- 智力題
- 面試題精選1
- 面試題精選2
- 問答篇
- 2025面試題收集
- Other
- markdown 書寫
- Git
- LaTex 常用命令
- Bugs