前端 關于事件的那些事
事件是各位前端大佬們工作中避免不了的東西,大家肯定都不陌生。那麼大家是否有更為細緻的了解事件呢。今天我們一起來探讨探讨,有寫的不好或者不對的地方,歡迎各位大佬修正。
事件,就是文檔或浏覽器視窗發生的一些特定的互動瞬間。也就是說html于javascript腳本之間的互動其實就是通過事件來完成的
那麼,想要了解事件,有些概念就必須知道了,分别是:
事件流
事件處理程式
事件對象
1、事件流是什麼
我們可以簡單的了解為 事件流就是描述從頁面中接收事件的順序
2、事件流有哪幾種
我們都知道有 事件冒泡 和 事件捕獲
其實最初 IE 和 Netscape所定義的事件流是不一樣
IE 的事件流指的是 事件冒泡
而Netscape得事件流指得是 事件捕獲
下面我們來詳細看看這兩種事件流
什麼是事件冒泡
即 事件開始由最具體的元素逐級向上傳播到最不具體的節點 也就是由内往外傳播事件的過程 事件冒泡所有浏覽器都支援,故比較常用
什麼是事件捕獲
即 事件開始由最不具體的元素逐級向下傳播到最具體的節點 也就是由外往内傳播事件的過程 事件捕獲得目的在于 事件到達預定目标之前捕獲它
因事件捕獲存在浏覽器相容性問題,故一般不推薦用
注意: 事件捕獲雖然是Netscape唯一支援的事件流 但是如今大部分主流浏覽器以及IE9及以上浏覽器都已經支援了
3、DOM事件流
首先 最初DOM0級事件認為 事件流是隻存在一個階段得 那就是事件冒泡
後來 DOM2級事件則規定 事件流分為3個階段 分别是 事件捕獲階段 處于目标事件階段 事件冒泡階段
注意: 在DOM事件流中,實際的目标在捕獲階段是不會接收到事件的,也就是說在捕獲階段,是不會觸發目标元素上的事件處理程式的。故目标元素的事件會在 第二個階段 處于目标階段發生,并進行事件處理。 并且 目标元素上的事件處理會被看做第三個階段(事件冒泡階段) 的一部分
但是: 盡管 DOM2級事件流規定捕獲階段不會涉及到事件目标 但是,IE9及以上以及大多數主流浏覽器(Safari、Chrome、Firefox 和 Opera 9.5 以及更高版本)都實作了在捕獲階段觸發事件對象上的事件。故最終,目标元素上的事件實際上在捕獲階段 和 冒泡階段都會被觸發
4、事件處理程式
什麼是事件處理程式
簡單了解就是 用來響應某個事件的函數 就叫做事件處理程式
事件處理程式我們可能接觸過幾種 比如
<body id="app">
<div class="myDiv">
myDiv
<button class="btn">目标元素</button>
</div>
</body>
<script>
var btn = document.querySelector('.btn')
var div = document.querySelector('.myDiv')
var body = document.getElementById('app')
btn.onclick = function () {
console.log('我是目标元素')
}
btn.addEventListener('click', function() {
console.log('我是目标元素')
}, false)
btn.attachEvent('onclick', function() {
console.log('我是目标元素')
})
</script>
那麼 這3種直接到底有什麼差別呢。下面我們來一一說明
DOM0級事件處理程式
首先,第一種
btn.onclick = function () {
console.log('我是目标元素')
}
這種是屬于DOM0級事件處理程式
特點 簡捷、所有浏覽器都支援 不存在相容性問題 并且函數内部this始終指向元素本身
DMM2級事件處理程式
DOM2級事件處理程式提供了添加事件以及移除事件兩個方法, 它們都接受3個參數,分别是 要處理的事件名 處理事件的函數 以及一個布爾值
btn.addEventListener('click', function() {
console.log('我是目标元素')
}, false)
btn.removeEventListener('click', function() {
console.log('移除事件')
}, false)
注意:
1、第三個參數 預設不傳時是false, 當為false時, 表示該事件處理程式在事件冒泡階段被調用, 當為true時,表示該事件處理程式在事件捕獲階段被調用
如下:我們做同一個操作,那就是用滑鼠去點選最内層的btn按鈕
當第三個參數為false時
btn.addEventListener('click', function () {
console.log('按鈕 被觸發了')
}, false)
div.addEventListener('click', function () {
console.log('div 被觸發了')
}, false)
body.addEventListener('click', function () {
console.log('body 被觸發了')
}, false)
當第三個參數為true時
btn.addEventListener('click', function () {
console.log('按鈕 被觸發了')
}, true)
div.addEventListener('click', function () {
console.log('div 被觸發了')
}, true)
body.addEventListener('click', function () {
console.log('body 被觸發了')
}, true)
2、這種方法存在相容性問題,IE9及以上、Firefox、Safari、Chrome 和 Opera等主流浏覽器才支援。
3、用addEventListener此方法添加的事件處理程式隻能使用removeEventListener來移除
并且,移除時,傳入的參數必須和添加事件處理程式時的參數完全相同(否則将會移除不成功)
那麼 什麼叫參數完全相同呢,我們來看個例子
btn.addEventListener('click', function() {
console.log('我是目标元素')
}, false)
btn.removeEventListener('click', function() {
console.log('我是目标元素')
}, false) // 沒有用
注意 上面例子中,表面上好像添加和删除時的參數是一樣的,但是,大家不要忘了,對象和函數是複雜資料類型,任何兩個複雜資料類型資料都是不完全相等的。因為它們比較的是指針,隻有兩個指向同一個記憶體空間的指針才是完全相等的。
是以,以上兩個方法的第二個參數(也就是那個處理函數)其實是不相等的。因為它們是匿名函數
是以,總結出一點
當你添加一個事件處理程式時,是以匿名函數的形式添加的,那麼這個事件處理程式将不可移除
那麼如何才能正确移除呢,看下面的方法
var handleFunction = function () {
console.log('我是目标元素')
}
btn.addEventListener('click', handleFunction, false)
btn.removeEventListener('click', handleFunction, false) // 可正常移除
最後多一句嘴: 上面我們說過,事件捕獲是存在相容性問題的,故,在不必要的情況下,請盡量将事件處理程式添加到事件流的冒泡階段。你懂的。。。
IE事件處理程式
IE的事件處理程式同樣提供了兩個方法
btn.attachEvent('onclick', function () {
console.log('btn 被觸發了')
})
btn.detachEvent('onclick', function () {
console.log('btn 被觸發了')
})
和addEvenListener以及removeEventListener不同的是:
1、IE的attachEvent 和 detachEvent隻有兩個參數,第一個是事件名,第二個是處理事件的函數。
2、由于 IE8 及更早版本隻支援事件冒泡,是以通過attachEvent()添加的事件處理程式都會被添加到冒泡階段。也就是說通過此方法添加的事件處理函數,隻有事件冒泡,沒有事件捕獲
3、通過attachEvent和detachEvent方法添加和移除事件時,事件名前面都必須加上單詞on
4、IE11以下版本(不包括11,IE11及以上和edge都已不支援)浏覽器均支援,其他浏覽器都不支援,用時請注意
5、addEventListener 和 removeEventListener 以及DOM0級事件處理程式 btn.οnclick= function() {} 内部的this都是=是指向觸發事件的元素的,而attachEvent 和 detachEvent 是始終在全局作用域下運作,故内部this 指向window
btn.attachEvent('onclick', function () {
console.log(this === window) // true
})
btn.detachEvent('onclick', function () {
console.log(this === window) // true
})
此時我們已經了解到了DOM0級的事件處理程式以及DOM2級的事件處理程式以及IE的事件處理,但是隻有DOM0級事件處理程式是跨浏覽器的,其他兩個都存在相容性問題。故,此時,我們是不是可以封裝一個新的對象方法,進而來實作跨浏覽器的事件處理呢,附上代碼如下:
var EvenItem = {
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;
}
}
}
var handler = function (event) {
console.log(event.type)
}
// 使用方式如下
// 添加事件
EventItem.addHandleer(btn, 'click', handler)
// 移除事件
EventItem.removeHandler(btn, 'click', handler)
5、事件對象
什麼是事件對象
簡單了解為 包含所有與事件相關的資訊的對象(包括事件的元素,事件類型,以及其他與事件有關的資訊)
在DOM事件觸發時,會産生這樣一個事件對象event
IE中的事件對象和其他浏覽器中的DOM事件對象有點不一樣。下面我們分别來細說
1、非IE浏覽器的事件對象
雖然對于不同的事件類型來說,事件對象中的屬性和方法可能會有不一樣的地方。但是不管哪種事件類型的事件對象,都會存在以下屬性和方法
我們挑幾個常用的來加以說明
a、preventDefault()
這個方法表示可以取消事件的預設行為,前提是必須cancelable屬性為true時,調用才能生效
比如:當我們點選一個a标簽時,會觸發a标簽跳轉herf屬性的url位址這個預設行為。當我們不需要産生跳轉時,就可以通過preventDefault()去取消這個預設行為
b、stopPropagation()
這個方法表示阻止事件繼續傳播(可能是阻止事件冒泡,也可能是事件捕獲),但前提是bubbles這個屬性為true時,調用此方法才能生效
c、currentTarget 和 target
這兩者有什麼差別呢
currentTarget: 目前正在處理事件的那個元素
target: 事件的目标
咋一看,好像沒太明白這兩個的差別。沒關心,我們看個例子就懂了
<body id="app">
<div class="myDiv">
myDiv
<button class="btn">目标元素</button>
</div>
</body>
<script>
var btn = document.querySelector('.btn')
var div = document.querySelector('.myDiv')
var body = document.getElementById('app')
// 我們做一個操作,那就是用滑鼠點選 目标元素 按鈕,由于事件冒泡的原因,故會同時觸發btn,div,body3個元素的點選事件
btn.onclick = function (event) {
console.log('我是目标元素')
console.log(event.currentTarget) // btn
console.log(event.target) // btn
console.log(event.currentTarget === this) // true
console.log(event.target === this) // true
}
div.addEventListener('click', function(event) {
console.log('我是div')
console.log(event.currentTarget) // div
console.log(event.target) // btn
console.log(event.currentTarget === this) // true
console.log(event.target === this) // false
}, false)
body.onclick = function (event) {
console.log('我是body')
console.log(event.currentTarget) // body
console.log(event.target) // btn
console.log(event.currentTarget === this) // true
console.log(event.target === this) // false
}
</script>
由以上代碼以及輸出結果我們就可以得出以下結論
event.target 表示的是真正被點選(或者其他操作)的元素 稱為事件目标(我們滑鼠點選的是 目标元素 那個按鈕,故無論冒泡到哪個元素上執行處理程式,target都始終為被滑鼠點選的那個 button)
而
event.currentTarget 表示的是目前正在處理事件的元素,故event.currentTarget始終等于this(比如 目前冒泡到body上觸發了body的事件處理程式,那麼對于這個事件處理程式而言,目前正在處理這個事件的元素就是body 故 event.currentTarget 就是body)
2、IE浏覽器的事件對象
首先,對于IE而言,不同的事件處理程式 event事件對象将有不同的差別
a、DOM0級事件處理程式,event對象将會作為window對象的一個屬性而存在
var btn = document.getElementById("myBtn");
btn.onclick = function(){
var event = window.event;
alert(event.type); //"click"
};
盡管如此,但IE9及以上版本的浏覽器實際也已經實作了 将event對象作為處理函數的參數傳回,具體看下面一段代碼
btn.onclick = function (event) {
console.log(event) // undefind IE9以下
console.log(event) // object IE9及以上
console.log(window.event) // object 所有IE浏覽器
}
b、attachEvent事件處理程式,event對象既作為處理函數的參數而存在,又作為window對象的一個屬性而存在
var btn = document.getElementById("myBtn");
btn.attachEvent('onclick', function(event) {
console.log(event) // object 所有支援attachEvent的IE浏覽器
console.log(window.event) // object 所有支援attachEvent的IE浏覽器 兩種方式均可擷取event
});
IE浏覽器的事件對象屬性和方法也會因為事件類型的不同而有所差別,但同樣,不管哪種事件類型的事件對象,都會存在以下屬性或方法
其中,針對srcElement,我稍微做個說明:
srcElement 是和target屬性相同的,而不是currentTarget,故srcElement表示的始終是真正被點選(或者其他操作)的目标元素
btn.attachEvent('onclick', function (event) {
console.log('我是目标元素')
console.log(event.srcElement) // btn
console.log(this) // window
console.log(event.srcElement === this) // false
})
div.attachEvent('onclick', function (event) {
console.log('我是div')
console.log(event.srcElement) // btn
console.log(this) // window
console.log(event.srcElement === this) // false
})
body.attachEvent('onclick', function (event) {
console.log('我是body')
console.log(event.srcElement) // btn
console.log(this) // window
console.log(event.srcElement === this) // false
})
3、跨浏覽器的事件對象
雖然 DOM 和 IE 中的 event 對象不同,但基于它們之間的相似性我們是不是也可以拿出跨浏覽器的方案來,附上代碼
var EvenItem = {
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;
}
},
getEvent: function (event) {
return event ? event : window.event
},
preventDefault : function (event) {
if (event.preventDefault) {
event.preventDefault()
} else {
event.returnValue = false
}
},
stopPropagation: function (event) {
if (event.stopPropagation) {
event.stopPropagation()
} else {
event.cancelBubble = true
}
}
}
var handler = function (event) {
console.log(event.type)
}