天天看點

關于事件那些事前端 關于事件的那些事

前端 關于事件的那些事

事件是各位前端大佬們工作中避免不了的東西,大家肯定都不陌生。那麼大家是否有更為細緻的了解事件呢。今天我們一起來探讨探讨,有寫的不好或者不對的地方,歡迎各位大佬修正。

事件,就是文檔或浏覽器視窗發生的一些特定的互動瞬間。也就是說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)
}
           

其實後面還有個事件類型,但是事件的類型太多了,我這裡就不做過多說明了,大家感興趣的可以自己找些資料去看看。關于事件,我們就說到這裡了。說的不好或不對的地方,歡迎留言指出,萬分感謝

繼續閱讀