該EventTarget方法addEventListener()設(shè)置一個(gè)函數(shù),只要將指定的事件傳遞給目標(biāo),就會(huì)調(diào)用該函數(shù)。共同目標(biāo)是Element,, Document和Window,但目標(biāo)可以是支持事件的任何對(duì)象(例如XMLHttpRequest)。
addEventListener()通過(guò)向調(diào)用它的EventTarget上的指定事件類型的事件偵聽器列表添加實(shí)現(xiàn)EventListener的函數(shù)或?qū)ο髞?lái)工作。
target.addEventListener(type, listener[, options]);
target.addEventListener(type, listener[, useCapture]);
target.addEventListener(type, listener[, useCapture, wantsUntrusted ]); // Gecko/Mozilla only
undefined
事件偵聽器可以指定為回調(diào)函數(shù)或?qū)崿F(xiàn)EventListener的對(duì)象,其handleEvent()方法用作回調(diào)函數(shù)。
回調(diào)函數(shù)本身具有與handleEvent()方法具有相同的參數(shù)和返回值;也就是說(shuō),回調(diào)接受一個(gè)參數(shù):一個(gè)基于Event描述已發(fā)生事件的對(duì)象,它不返回任何內(nèi)容。
例如,可以使用一個(gè)事件處理程序回調(diào)同時(shí)處理fullscreenchange和fullscreenerror,可能是如下所示:
function eventHandler(event) {
if (event.type == fullscreenchange) {
/* handle a full screen toggle */
} else /* fullscreenerror */ {
/* handle a full screen toggle error */
}
}
在舊版本的DOM規(guī)范中,第三個(gè)參數(shù)addEventListener()是一個(gè)布爾值,指示是否使用捕獲。隨著時(shí)間的推移,很明顯需要更多的選擇。不是向函數(shù)添加更多參數(shù)(在處理可選值時(shí)使事情變得非常復(fù)雜),而是將第三個(gè)參數(shù)更改為一個(gè)對(duì)象,該對(duì)象可以包含定義選項(xiàng)值的各種屬性,以配置刪除事件偵聽器的過(guò)程。
因?yàn)榕f版瀏覽器(以及一些不太舊的瀏覽器)仍假設(shè)第三個(gè)參數(shù)是布爾值,所以您需要構(gòu)建代碼以智能地處理此場(chǎng)景。您可以通過(guò)對(duì)您感興趣的每個(gè)選項(xiàng)使用特征檢測(cè)來(lái)執(zhí)行此操作。
例如,如果要檢查passive選項(xiàng):
var passiveSupported = false;
try {
var options = {
get passive() { // This function will be called when the browser
// attempts to access the passive property.
passiveSupported = true;
}
};
window.addEventListener("test", options, options);
window.removeEventListener("test", options, options);
} catch(err) {
passiveSupported = false;
}
這將創(chuàng)建一個(gè)具有該passive屬性的getter函數(shù)的options對(duì)象;getter設(shè)置一個(gè)標(biāo)志,passiveSupported,如果它被調(diào)用,則passiveSupported為true。這意味著如果瀏覽器檢查對(duì)象passive上options屬性的值,passiveSupported則將其設(shè)置為true;否則,它將保持為false。然后我們調(diào)用addEventListener()來(lái)設(shè)置假的事件處理程序,指定這些選項(xiàng),以便在瀏覽器將對(duì)象識(shí)別為第三個(gè)參數(shù)時(shí)檢查選項(xiàng)。然后,我們調(diào)用removeEventListener()給自己清理。(注意,handleEvent()在未調(diào)用的事件偵聽器上會(huì)被忽略。)
您可以通過(guò)這種方式檢查是否支持任何選項(xiàng)。只需使用類似于上面顯示的代碼為該選項(xiàng)添加一個(gè)getter。
然后,當(dāng)您想要?jiǎng)?chuàng)建使用相關(guān)選項(xiàng)的實(shí)際事件偵聽器時(shí),您可以執(zhí)行以下操作:
someElement.addEventListener("mouseup", handleMouseUp, passiveSupported
? { passive: true } : false);
這里我們?yōu)閟omeElement元素上的mouseup事件添加一個(gè)監(jiān)聽器。對(duì)于第三個(gè)參數(shù),如果passiveSupported是true,我們指定一個(gè)passive設(shè)置為true的options對(duì)象;否則,我們知道我們需要傳遞一個(gè)布爾值,并且我們傳遞false作為useCapture參數(shù)的值。
如果您愿意,可以使用Modernizr或Detect It等第三方庫(kù)為您進(jìn)行此測(cè)試。
此示例演示如何使用addEventListener()監(jiān)視鼠標(biāo)對(duì)元素的單擊。
<table id="outside">
<tr><td id="t1">one</td></tr>
<tr><td id="t2">two</td></tr>
</table>
// Function to change the content of t2
function modifyText() {
var t2 = document.getElementById("t2");
if (t2.firstChild.nodeValue == "three") {
t2.firstChild.nodeValue = "two";
} else {
t2.firstChild.nodeValue = "three";
}
}
// add event listener to table
var el = document.getElementById("outside");
el.addEventListener("click", modifyText, false);
在此代碼中,modifyText()是使用addEventListener()注冊(cè)的click事件的偵聽器。單擊表格中的任何位置都會(huì)冒泡到處理程序,并運(yùn)行modifyText()。
在這里,我們將看看如何使用anonymous函數(shù)將參數(shù)傳遞給事件監(jiān)聽器。
<table id="outside">
<tr><td id="t1">one</td></tr>
<tr><td id="t2">two</td></tr>
</table>
// Function to change the content of t2
function modifyText(new_text) {
var t2 = document.getElementById("t2");
t2.firstChild.nodeValue = new_text;
}
// Function to add event listener to table
var el = document.getElementById("outside");
el.addEventListener("click", function(){modifyText("four")}, false);
請(qǐng)注意,偵聽器是一個(gè)匿名函數(shù),它封裝了代碼,然后代碼可以向modifyText()函數(shù)發(fā)送參數(shù),該函數(shù)負(fù)責(zé)實(shí)際響應(yīng)事件。
此示例演示了使用arrow函數(shù)表示法實(shí)現(xiàn)的簡(jiǎn)單事件偵聽器。
<table id="outside">
<tr><td id="t1">one</td></tr>
<tr><td id="t2">two</td></tr>
</table>
// Function to change the content of t2
function modifyText(new_text) {
var t2 = document.getElementById("t2");
t2.firstChild.nodeValue = new_text;
}
// Add event listener to table with an arrow function
var el = document.getElementById("outside");
el.addEventListener("click", () => { modifyText("four"); }, false);
請(qǐng)注意,雖然anonymous和arrow函數(shù)相似,但它們具有不同的this綁定。雖然anonymous(和所有傳統(tǒng)的JavaScript函數(shù))創(chuàng)建自己的this綁定,但arrow函數(shù)繼承了包含函數(shù)的this綁定。
這意味著當(dāng)使用arrow函數(shù)時(shí),包含函數(shù)可用的變量和常量也可用于事件處理程序。
<div class="outer">
outer, once & none-once
<div class="middle" target="_blank">
middle, capture & none-capture
<a class="inner1" rel="external nofollow" target="_blank" target="_blank">
inner1, passive & preventDefault(which is not allowed)
</a>
<a class="inner2" rel="external nofollow" target="_blank" target="_blank">
inner2, none-passive & preventDefault(not open new page)
</a>
</div>
</div>
.outer, .middle, .inner1, .inner2 {
display:block;
width:520px;
padding:15px;
margin:15px;
text-decoration:none;
}
.outer{
border:1px solid red;
color:red;
}
.middle{
border:1px solid green;
color:green;
width:460px;
}
.inner1, .inner2{
border:1px solid purple;
color:purple;
width:400px;
}
let outer = document.getElementsByClassName('outer') [0];
let middle = document.getElementsByClassName('middle')[0];
let inner1 = document.getElementsByClassName('inner1')[0];
let inner2 = document.getElementsByClassName('inner2')[0];
let capture = {
capture : true
};
let noneCapture = {
capture : false
};
let once = {
once : true
};
let noneOnce = {
once : false
};
let passive = {
passive : true
};
let nonePassive = {
passive : false
};
outer .addEventListener('click', onceHandler, once);
outer .addEventListener('click', noneOnceHandler, noneOnce);
middle.addEventListener('click', captureHandler, capture);
middle.addEventListener('click', noneCaptureHandler, noneCapture);
inner1.addEventListener('click', passiveHandler, passive);
inner2.addEventListener('click', nonePassiveHandler, nonePassive);
function onceHandler(event)
{
alert('outer, once');
}
function noneOnceHandler(event)
{
alert('outer, none-once, default');
}
function captureHandler(event)
{
//event.stopImmediatePropagation();
alert('middle, capture');
}
function noneCaptureHandler(event)
{
alert('middle, none-capture, default');
}
function passiveHandler(event)
{
// Unable to preventDefault inside passive event listener invocation.
event.preventDefault();
alert('inner1, passive, open new page');
}
function nonePassiveHandler(event)
{
event.preventDefault();
//event.stopPropagation();
alert('inner2, none-passive, default, not open new page');
}
在options對(duì)象中使用特定值之前,最好確保用戶的瀏覽器支持它,因?yàn)檫@并非所有瀏覽器都支持的附加功能。
addEventListener()是注冊(cè)W3C DOM中指定的事件偵聽器的方法。好處如下:
注冊(cè)事件偵聽器的另一種舊方法如下所述。
如果在處理事件的過(guò)程中向EventTarget添加了一個(gè)EventListener,則該事件不會(huì)觸發(fā)偵聽器。但是,在事件流的后期階段(例如冒泡階段)可能會(huì)觸發(fā)相同的偵聽器。
如果在有相同參數(shù)的相同EventTarget上注冊(cè)了多個(gè)相同的EventListener s ,則丟棄重復(fù)的實(shí)例。它們不會(huì)導(dǎo)致EventListener被調(diào)用兩次,并且不需要使用該removeEventListener()方法手動(dòng)刪除它們。但請(qǐng)注意,當(dāng)使用anonymous函數(shù)作為處理程序時(shí),這樣的偵聽器將不相同,因?yàn)閍nonymous函數(shù)是不相同的,即使使用簡(jiǎn)單地重復(fù)調(diào)用的SAME不變的源代碼定義,即使在循環(huán)中也是如此。然而,在這種情況下重復(fù)定義相同的命名函數(shù)可能更成問(wèn)題。
通常需要引用觸發(fā)事件處理程序的元素,例如對(duì)一組類似元素使用泛型處理程序時(shí)。
如果使用addEventListener()將處理程序函數(shù)附加到元素,則處理程序內(nèi)部的this值是對(duì)元素的引用。它與傳遞給處理程序的event參數(shù)的currentTarget屬性值相同。
如果在HTML源代碼中的元素上指定了事件處理程序(例如,onclick),則屬性值中的JavaScript代碼將有效地包裝在處理函數(shù)中,該函數(shù)以與addEventListener() 一致的方式綁定this值。this代碼中出現(xiàn)的內(nèi)容表示對(duì)元素的引用。請(qǐng)注意,this函數(shù)內(nèi)部的值(由屬性值中的代碼調(diào)用)的行為與標(biāo)準(zhǔn)規(guī)則相同。這在以下示例中顯示:
<table id="t" onclick="modifyText();">
. . .
帶有modifyText()的this的值是對(duì)全局對(duì)象Window的引用(或者在嚴(yán)格模式的情況下的undefined)。
該Function.prototype.bind()方法允許您指定應(yīng)該用于對(duì)給定函數(shù)的所有調(diào)用的this值。這使您可以輕松繞過(guò)不清楚this將要發(fā)生什么的問(wèn)題,具體取決于調(diào)用函數(shù)的上下文。但請(qǐng)注意,您需要保留對(duì)偵聽器的引用,以便稍后將其刪除。
這是一個(gè)有和沒(méi)有bind()的例子:
var Something = function(element) {
// |this| is a newly created object
this.name = 'Something Good';
this.onclick1 = function(event) {
console.log(this.name); // undefined, as |this| is the element
};
this.onclick2 = function(event) {
console.log(this.name); // 'Something Good', as |this| is bound to newly created object
};
element.addEventListener('click', this.onclick1, false);
element.addEventListener('click', this.onclick2.bind(this), false); // Trick
}
var s = new Something(document.body);
另一個(gè)解決方案是使用一個(gè)特殊的函數(shù)handleEvent()來(lái)捕獲任何事件:
var Something = function(element) {
// |this| is a newly created object
this.name = 'Something Good';
this.handleEvent = function(event) {
console.log(this.name); // 'Something Good', as this is bound to newly created object
switch(event.type) {
case 'click':
// some code here...
break;
case 'dblclick':
// some code here...
break;
}
};
// Note that the listeners in this case are |this|, not this.handleEvent
element.addEventListener('click', this, false);
element.addEventListener('dblclick', this, false);
// You can properly remove the listeners
element.removeEventListener('click', this, false);
element.removeEventListener('dblclick', this, false);
}
var s = new Something(document.body);
處理對(duì)this的引用的另一種方法是向EventListener傳遞一個(gè)函數(shù),該函數(shù)調(diào)用包含需要訪問(wèn)的字段的對(duì)象的方法:
class SomeClass {
constructor() {
this.name = 'Something Good';
}
register() {
var that = this;
window.addEventListener('keydown', function(e) {return that.someMethod(e);});
}
someMethod(e) {
console.log(this.name);
switch(e.keyCode) {
case 5:
// some code here...
break;
case 6:
// some code here...
break;
}
}
}
var myObject = new SomeClass();
myObject.register();
在IE 9之前的Internet Explorer版本中,您必須使用attachEvent()而不是標(biāo)準(zhǔn)addEventListener()。對(duì)于IE,我們將前面的示例修改為:
if (el.addEventListener) {
el.addEventListener('click', modifyText, false);
} else if (el.attachEvent) {
el.attachEvent('onclick', modifyText);
}
attachEvent()有一個(gè)缺點(diǎn):this的值將是window對(duì)象的引用,而不是引發(fā)它的元素。
該attachEvent()方法可以與onresize事件配對(duì)以檢測(cè)何時(shí)調(diào)整網(wǎng)頁(yè)中的某些元素的大小。專門的mselementresize事件與注冊(cè)事件處理程序的addEventListener方法配合使用時(shí),提供與onresize調(diào)整某些HTML元素大小時(shí)相同的功能。
通過(guò)在腳本開頭使用以下代碼,您可以解決addEventListener(),removeEventListener(),Event.preventDefault(),和Event.stopPropagation()不被Internet Explorer 8支持的問(wèn)題。該代碼支持使用handleEvent()和DOMContentLoaded事件。
注意:useCapture不支持,因?yàn)镮E 8沒(méi)有任何替代方法。以下代碼僅添加了IE 8支持。此IE 8 polyfill僅適用于標(biāo)準(zhǔn)模式:需要doctype聲明。
(function() {
if (!Event.prototype.preventDefault) {
Event.prototype.preventDefault=function() {
this.returnValue=false;
};
}
if (!Event.prototype.stopPropagation) {
Event.prototype.stopPropagation=function() {
this.cancelBubble=true;
};
}
if (!Element.prototype.addEventListener) {
var eventListeners=[];
var addEventListener=function(type,listener /*, useCapture (will be ignored) */) {
var self=this;
var wrapper=function(e) {
e.target=e.srcElement;
e.currentTarget=self;
if (typeof listener.handleEvent != 'undefined') {
listener.handleEvent(e);
} else {
listener.call(self,e);
}
};
if (type=="DOMContentLoaded") {
var wrapper2=function(e) {
if (document.readyState=="complete") {
wrapper(e);
}
};
document.attachEvent("onreadystatechange",wrapper2);
eventListeners.push({object:this,type:type,listener:listener,wrapper:wrapper2});
if (document.readyState=="complete") {
var e=new Event();
e.srcElement=window;
wrapper2(e);
}
} else {
this.attachEvent("on"+type,wrapper);
eventListeners.push({object:this,type:type,listener:listener,wrapper:wrapper});
}
};
var removeEventListener=function(type,listener /*, useCapture (will be ignored) */) {
var counter=0;
while (counter<eventListeners.length) {
var eventListener=eventListeners[counter];
if (eventListener.object==this && eventListener.type==type && eventListener.listener==listener) {
if (type=="DOMContentLoaded") {
this.detachEvent("onreadystatechange",eventListener.wrapper);
} else {
this.detachEvent("on"+type,eventListener.wrapper);
}
eventListeners.splice(counter, 1);
break;
}
++counter;
}
};
Element.prototype.addEventListener=addEventListener;
Element.prototype.removeEventListener=removeEventListener;
if (HTMLDocument) {
HTMLDocument.prototype.addEventListener=addEventListener;
HTMLDocument.prototype.removeEventListener=removeEventListener;
}
if (Window) {
Window.prototype.addEventListener=addEventListener;
Window.prototype.removeEventListener=removeEventListener;
}
}
})();
addEventListener()是隨著DOM 2 Events規(guī)范引入的。在此之前,事件監(jiān)聽器注冊(cè)如下:
// Passing a function reference — do not add '()' after it, which would call the function!
el.onclick = modifyText;
// Using a function expression
element.onclick = function() {
// ... function logic ...
};
如果有click的話,此方法將替換元素上的現(xiàn)有click事件偵聽器。其他事件和相關(guān)的事件處理程序(如blur(onblur)和keypress(onkeypress))的行為類似。
因?yàn)樗举|(zhì)上是DOM 0的一部分,所以這種添加事件監(jiān)聽器的技術(shù)得到了廣泛的支持,并且不需要特殊的跨瀏覽器代碼。它通常用于動(dòng)態(tài)注冊(cè)事件偵聽器,除非需要額外的addEventListener()功能。
var i;
var els = document.getElementsByTagName('*');
// Case 1
for(i=0 ; i<els.length ; i++){
els[i].addEventListener("click", function(e){/*do something*/}, false);
}
// Case 2
function processEvent(e){
/*do something*/
}
for(i=0 ; i<els.length ; i++){
els[i].addEventListener("click", processEvent, false);
}
在上面的第一種情況中,每次迭代循環(huán)都會(huì)創(chuàng)建一個(gè)新的(匿名)處理函數(shù)。在第二種情況下,相同的先前聲明的函數(shù)被用作事件處理程序,這導(dǎo)致較小的內(nèi)存消耗,因?yàn)橹粍?chuàng)建了一個(gè)處理函數(shù)。此外,在第一種情況下,無(wú)法調(diào)用removeEventListener(),因?yàn)椴槐A魧?duì)anonymous函數(shù)的引用(或者這里,不保留循環(huán)可能創(chuàng)建的任何多個(gè)anonymous函數(shù)。);在第二種情況下,可以執(zhí)行myElement.removeEventListener("click", processEvent, false) 因?yàn)閜rocessEvent是函數(shù)引用。
實(shí)際上,關(guān)于內(nèi)存消耗,缺乏保留函數(shù)引用不是真正的問(wèn)題;而是缺乏保持STATIC函數(shù)引用。在下面的兩個(gè)問(wèn)題情況中,都保留了函數(shù)引用,但由于它在每次迭代時(shí)重新定義,因此它不是靜態(tài)的。在第三種情況下,每次迭代都會(huì)重新分配對(duì)anonymous函數(shù)的引用。在第四種情況下,整個(gè)函數(shù)定義是不變的,但它仍然被重復(fù)定義為new(除非它被編譯器[[promote]],因此不是靜態(tài)的。因此,盡管看起來(lái)只是[[Multiple identical event listeners]],但在這兩種情況下,每次迭代都會(huì)創(chuàng)建一個(gè)新的偵聽器,它具有對(duì)處理函數(shù)的唯一引用。但是,由于函數(shù)定義本身不會(huì)改變,
同樣在這兩種情況下,由于函數(shù)引用被保留但每次添加都重復(fù)定義,上面的remove-statement仍然可以刪除一個(gè)偵聽器,但現(xiàn)在只添加了最后一個(gè)。
// For illustration only: Note "MISTAKE" of [j] for [i] thus causing desired events to all attach to SAME element
// Case 3
for(var i=0, j=0 ; i<els.length ; i++){
/*do lots of stuff with j*/
els[j].addEventListener("click", processEvent = function(e){/*do something*/}, false);
}
// Case 4
for(var i=0, j=0 ; i<els.length ; i++){
/*do lots of stuff with j*/
function processEvent(e){/*do something*/};
els[j].addEventListener("click", processEvent, false);
}
根據(jù)規(guī)范,該passive選項(xiàng)的默認(rèn)值始終為false。但是,這引入了處理某些觸摸事件(以及其他)的事件偵聽器在嘗試處理滾動(dòng)時(shí)阻止瀏覽器的主線程的可能性,從而導(dǎo)致滾動(dòng)處理期間性能可能大大降低。
為了避免這個(gè)問(wèn)題,一些瀏覽器(具體而言,Chrome和Firefox)已經(jīng)將文檔級(jí)節(jié)點(diǎn)Window,Document和Document.body中的touchstart和touchmove事件的passive選項(xiàng)的默認(rèn)值更改為true。這可以防止調(diào)用事件偵聽器,因此在用戶滾動(dòng)時(shí)無(wú)法阻止頁(yè)面呈現(xiàn)。
注意:如果您需要知道哪些瀏覽器(或這些瀏覽器的哪些版本)實(shí)現(xiàn)了這種更改的行為,請(qǐng)參閱下面的兼容性表。
您可以通過(guò)顯式設(shè)置passiveto的值為false來(lái)覆蓋此行為,如下所示:
/* Feature detection */
var passiveIfSupported = false;
try {
window.addEventListener("test", null, Object.defineProperty({}, "passive", { get: function() { passiveIfSupported = { passive: true }; } }));
} catch(err) {}
window.addEventListener('scroll', function(event) {
/* do something */
// can't use event.preventDefault();
}, passiveIfSupported );
在不支持addEventListener()的options參數(shù)的舊瀏覽器上,嘗試使用它會(huì)阻止useCapture參數(shù)的使用,而無(wú)需正確使用特性檢測(cè)。
對(duì)于基本scroll事件,您無(wú)需擔(dān)心passive的值。由于無(wú)法取消,因此事件偵聽器無(wú)法阻止頁(yè)面呈現(xiàn)。
規(guī)格 | 狀態(tài) | 注釋 |
---|---|---|
DOM 該規(guī)范中'EventTarget.addEventListener()'的定義。 |
Living Standard
|
|
DOM4 該規(guī)范中“EventTarget.addEventListener()”的定義。 |
Obsolete
|
|
文檔對(duì)象模型(DOM)級(jí)別2事件規(guī)范 該規(guī)范 中“EventTarget.addEventListener()”的定義。 |
Obsolete
|
初步定義 |
電腦端 | 移動(dòng)端 | |||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
Chrome
|
Edge
|
Firefox
|
Internet Explorer
|
Opera
|
Safari
|
Android webview | Chrome for Android
|
Edge Mobile | Firefox for Android
|
Opera for Android
|
iOS Safari | |
基本支持 | 支持:1 |
支持:12 | 支持:1 | 支持:9 | 支持:7 | 支持:1 | 支持:1 |
支持:18 |
支持 | 支持:4 | 支持:7 | 支持:1 |
useCapture 參數(shù)可選 | 支持:1 | 支持 | 支持:6 | 支持:9 | 支持:11.6 | 支持 | 支持:1 | 支持:18 | 支持 | 支持:6 | 支持:11.6 | 支持 |
options對(duì)象支持的表單(第三個(gè)參數(shù)可以是Boolean,也可以是選項(xiàng),以便向后兼容) | 支持:49 | 支持 | 支持:49 | 不支持號(hào) | 支持 | 支持:10 | 支持:49 | 支持:49 | 支持 | 支持:49 | 支持 | 支持:10 |
options:capture選項(xiàng) | 支持:52 | 支持 | 支持 | 不支持 | 支持 | 支持 | 支持:52 | 支持:52 | 支持 | 支持 | 支持 | 支持 |
options:once選項(xiàng) | 支持:55 | 支持 | 支持:50 | 不支持 | 支持:42 | 支持 | 支持:55 | 支持:55 | 支持 | 支持:50 | 支持:42 | 支持 |
options:passive選項(xiàng) | 支持:51 | 支持 | 支持 | 不支持 | 支持 | 支持 | 支持:51 | 支持:51 | 支持 | 支持 | 支持 | 支持 |
options:passive選項(xiàng)默認(rèn)為truefortouchstart和touchmoveevents | 支持:55 | 不支持 | 支持:61 | 不支持 | ? | 不支持 | 支持:55 | 支持:55 | 不支持 | 支持:61 | ? | 不支持 |
更多建議: