JavaScript DOM 事件模型
阿新 • • 發佈:2020-04-03
# JavaScript DOM 事件模型
> JavaScript 是基於面向物件和事件驅動的一門語言,事件模型是 DOM 中至關重要的內容,理解事件驅動機制、事件反饋、事件冒泡、事件捕獲以及事件委託能幫助我們更好的處理事件,寫出更優的程式碼
## 事件驅動機制
1. 當事件發生時,我們收到事件的反饋,在 JavaScript 中,事件反饋是我們自行定義的事件處理函式
2. 事件,如點選事件、滑鼠移入事件等,是每一個元素與生俱來的能力
3. 通常說的繫結事件,實際上是繫結事件的反饋,即事件處理函式
4. 例如點選一個按鈕,按鈕元素物件是事件傳送器或事件源,事件是滑鼠點選事件,事件處理函式是偵聽器
5. 元素物件發出事件,事件處理函式做出反應,這就是 JS 的事件驅動機制
> 在觀察者模式中,事件傳送器就是主題,事件處理函式即偵聽器就是觀察者
## 繫結事件反饋
1. 內聯屬性
```html
```
介於結構和邏輯要相分離,不建議使用內聯方式繫結
2. 事件控制代碼
```javascript
var oBtn = document.getElementsByTagName('button')[0];
oBtn.onclick = function() {
// this -> oBtn
}
```
相容性好,但是重複繫結會覆蓋
3. 事件監聽器
```javascript
var oBtn = document.getElementsByTagName('button')[0];
oBtn.addEventListener("click", funtion(){
// this -> oBtn
}, false);
oBtn.addEventListener("click", test, false);
funtion test(){
// 事件處理函式
}
```
重複新增,不會覆蓋之前新增的監聽器,但是如果事件型別、事件處理函式和最後一個布林引數都相同,則不會重複執行
IE8 及以下不支援 addEventListener,可用 attachEvent 代替
```javascript
var oBtn = document.getElementsByTagName('button')[0];
oBtn.attachEvent("onclick", funtion(){
// this -> window
});
// 區別於 addEventListener,第一個引數使用 'onclick',而不是 'click'
// 並且內部 this 指向 window
```
相容性封裝
```javascript
function addEvent(elem, type, fn) {
if (elem.addEventListener) {
elem.addEventListener(type, fn, false);
} else if (elem.attachEvent) {
elem.attachEvent('on' + type, function(ev) {
fn.call(elem, ev); // call 相容性比 bind 好
});
} else {
elem['on' + type] = fn;
}
}
```
4. 解除繫結
```javascript
oBtn.onclik = null;
oBtn.removeEventListener("click", test, false); // 解除 addEventListener
oBtn.detachEvent('onclick', test); // 解除 attachEvent
```
示例:點選一次後清除事件反饋
```javascript
oBtn.onclik = function() {
// ...
this.onclick = null;
}
// 非嚴格模式
oBtn.addEventListener("click", funtion() {
// ...
this.removEventListener('cilck', arguments.callee, false);
}, false);
// 嚴格模式
oBtn.addEventListener("click", funtion temp() {
// ...
this.removeEventListener('click', temp, false);
}, false);
```
## 事件冒泡和捕獲
1. 事件冒泡:當一個元素髮生事件時,該事件會向父級元素傳遞,按由子到父的順序觸發一連串的事件反饋,稱之為事件冒泡
DOM 上的巢狀關係會產生事件冒泡,例如兩個 div 巢狀,點選內部的 div,觸發內部 div 的點選事件,內部 div 的點選事件處理函式進行響應,這個事件向其父級即外部 div 傳遞,外部 div 也有點選事件,外部 div 所繫結的點選事件反饋也會響應
```html
```
```javascript
var outer = document.getElementsByClassName('outer')[0],
inner = outer.getElementsByClassName('inner')[0];
outer.addEventListener('click', function () {
console.log('bubble outer');
}, false);
inner.addEventListener('click', function () {
console.log('bubble inner');
}, false);
// addEventListener 最後一個引數預設值為 false,表示事件冒泡
// 點選 inner,打印出
// bubble inner
// bubble outer
```
2. 事件捕獲:當一個元素髮生事件時,該事件會向父級元素傳遞,按由父到子的順序觸發一連串的事件反饋,稱之為事件捕獲
事件捕獲與事件冒泡的觸發順序相反,同樣需要 DOM 上的巢狀關係
```javascript
outer.addEventListener('click', function () {
console.log('outer');
}, true);
inner.addEventListener('click', function () {
console.log('inner');
}, true);
// addEventListener 最後一個引數使用 true,表示事件捕獲
// 點選 inner,打印出
// outer
// in
```
3. 捕獲和冒泡的執行順序
```javascript
outer.addEventListener('click', function () {
console.log('bubble outer');
}, false); // 冒泡
inner.addEventListener('click', function () {
console.log('bubble inner');
}, false); // 冒泡
outer.addEventListener('click', function () {
console.log('outer');
}, true); // 捕獲
inner.addEventListener('click', function () {
console.log('inner');
}, true); // 捕獲
// 點選 inner,打印出
// outer
// bubble inner
// inner
// bubble outer
```
點選一個元素,元素即事件源,若事件源綁定了事件處理函式,且設定了事件捕獲,則先執行捕獲,捕獲執行完畢後,按照繫結順序執行該事件源繫結的事件,如果設定了事件冒泡,再執行冒泡
4. focus blur change submit reset select 事件沒有冒泡和捕獲,IE 瀏覽器沒有事件捕獲
## 阻止事件冒泡
1. 阻止冒泡的方法
Event 的原型上有 stopPropagation 方法,可以阻止冒泡,是 w3c 的規範
Event 的原型上有 cancleBubble 屬性,賦值為 true,可以阻止冒泡
2. addEventListener 繫結事件處理函式,拿到事件物件
```javascript
var outer = document.getElementsByClassName('outer')[0],
inner = outer.getElementsByClassName('inner')[0];
inner.addEventListener('click', function (ev) {
console.log(ev); // 事件物件 ev
ev.stopPropagation(); // 阻止事件冒泡
}, false);
```
3. IE 瀏覽器沒有 stopPropagation 方法,可以使用 cancelBubble 屬性
注意:IE 瀏覽器中事件物件存放在 window.event 中。IE8 不支援 addEventListener 方法
```javascript
// 封裝阻止冒泡的方法
function cancelBubble(ev) {
if (ev.stopPropagation) {
ev.stopPropagation();
} else ev.cancelBubble = true; // 相容 IE8 及以下
}
// 使用上文中封裝好的 addEvent 方法
function addEvent(elem, type, fn) {
if (elem.addEventListener) {
elem.addEventListener(type, fn);
} else if (elem.attachEvent) {
elem.attachEvent('on' + type, function (ev) {
fn.call(elem, ev);
});
} else {
elem['on' + type] = fn;
}
}
// 繫結事件處理函式
var outer = document.getElementsByClassName('outer')[0],
inner = outer.getElementsByClassName('inner')[0];
addEvent(inner, 'click', function (ev) {
var ev = ev || window.event; // IE 相容性寫法
cancelBubble(ev); // 阻止冒泡
});
```
## 阻止預設事件
1. 三種方法
- 事件物件 preventDefault() 方法,相容 IE9 及以上
- 事件物件 returnValue = false,相容 IE8 及以下
- 事件處理函式 return false
2. 相容性寫法
```javascript
function preventDefaultEvent(ev) {
if (ev.preventDefault) {
ev.preventDefault();
} else ev.returnValue = false; // 相容 IE8 及以下
}
```
3. 右鍵選單事件
```javascript
document.oncontextmenu = function (ev) {
var ev = ev || window.event;
// 1. ev.preventDefault(); // IE9 及以上
// 2. ev.returnValue = false; // IE8 及以下
// 3. return false;
}
```
4. a 標籤跳轉事件
href 使用偽協議
```html
a 標籤
a 標籤
a 標籤
```
onclick 事件 return false
```html
a 標籤
a 標籤
```
繫結事件處理函式
```html
a 標籤
```
> 表單的 action 屬性支援 `javascript:` 偽協議,onsubmit 或者提交按鈕點選事件都可以繫結處理函式,阻止提交的方法和阻止 a 標籤跳轉的方法類似
## 冒泡捕獲流
1. 事件流:描述從頁面中接收事件的順序
2. 事件冒泡流:微軟 IE 提出,Event Bubbling
3. 事件捕獲流:網景 Netscape 提出,Event Capturing
4. 事件流三個階段:事件捕獲階段、處於目標階段、事件冒泡階段
元素觸發事件時,首先事件捕獲階段,由父到子的執行事件處理函式,然後處於目標階段,該元素的事件處理函式按繫結順序執行,最後事件冒泡階段,由子到父的執行事件處理函式
## 事件和事件源
1. 事件即事件物件,可以由事件處理函式的引數拿到
IE8 及以下中事件物件存放在 window.event 中
```javascript
// btn 按鈕元素
btn.onclick = function(ev) {
var ev = ev || window.event; // IE8 相容性寫法
}
```
2. 事件源即事件源物件,是發生事件的元素,即事件傳送器,可以從事件物件中獲取
IE8 及以下只有 srcElement,firefox 低版本只有 target,chrome 兩者都有
```javascript
// btn 按鈕元素
btn.onclick = function(ev) {
var ev = ev || window.event; // IE8 相容性寫法
var tar = ev.target || ev.srcElement; // 獲取事件源的相容性寫法
}
```
## 事件委託
1. 事件委託也叫事件代理,指對父級元素繫結事件處理函式,通過獲取事件源來處理子元素
2. 示例:點選按鈕使列表 ul 增加 li 元素,點選每個 li 元素打印出其中的內容(innerHTML)
如果不使用事件委託,需要迴圈對每個 li 進行繫結,點選按鈕新增新的 li 元素後也要進行繫結,效率低下
使用事件委託,直接對 ul 繫結點選事件處理函式,獲取事件物件、事件源物件,再對源物件進行處理
```html
- 1
- 2
- 3
- 4