EventTarget方法:addEventListener()
该EventTarget方法addEventListener()设置一个函数,只要将指定的事件传递给目标,就会调用该函数。共同目标是Element,, Document和Window,但目标可以是支持事件的任何对象(例如XMLHttpRequest)。
addEventListener()通过向调用它的EventTarget上的指定事件类型的事件侦听器列表添加实现EventListener的函数或对象来工作。
语法
target.addEventListener(type, listener[, options]);
target.addEventListener(type, listener[, useCapture]);
target.addEventListener(type, listener[, useCapture, wantsUntrusted ]); // Gecko/Mozilla only
参数部分
- type
- 一个区分大小写的字符串,表示要侦听的事件类型。
- listener
- 当发生指定类型的事件时接收通知(实现Event接口的对象)的对象。这必须是实现EventListener接口的对象,或JavaScript函数。
- options (可选)
- 一个options对象,它指定有关事件侦听器的特征。可用选项包括:
- capture:Boolean,表示在将这种类型的事件将被调度到已注册的listener,在被调度到DOM树下面的任何EventTarget之前。
- once:Boolean,表示listener应该在添加后最多调用一次。如果为true,则会在调用时自动删除listener。
- passive:Boolean,如果为true,则表示由listener指定的函数永远不会调用preventDefault()。如果被动侦听器进行了调用preventDefault(),则除了生成控制台警告之外,用户代理将不执行任何操作。
- mozSystemGroup:Boolean,表示应将侦听器添加到系统组。仅适用于在XBL中运行的代码或Firefox浏览器的chrome中。
- useCapture (可选)
- 一个Boolean,指示是否在将这种类型的事件将被调度到已注册的listener,在被调度到DOM树下面的任何EventTarget之前。向上冒泡树的事件不会触发指定使用捕获的侦听器。当两个元素都已为该事件注册了句柄时,事件冒泡和捕获是两种传播事件的方式,这些事件发生在嵌套在另一个元素中的元素中。事件传播模式确定元素接收事件的顺序。如果未指定,则useCapture默认为false。
-
注意:对于附加到事件目标的事件侦听器,事件处于目标阶段,而不是捕获和冒泡阶段。无论useCapture参数如何,目标阶段中的事件将按照它们注册的顺序触发元素上的所有侦听器。注意: useCapture并非总是可选的。理想情况下,您应该将其包含在最广泛的浏览器兼容性中。
- wantsUntrusted
- Firefox(Gecko)特有的参数。如果为true,侦听器接收由Web内容调度的合成事件(在浏览器chrom中默认为false,常规网页中默认是true)。此参数对于加载项中的代码以及浏览器本身很有用。
返回值部分
undefined
使用说明
事件监听器回调部分
事件侦听器可以指定为回调函数或实现EventListener的对象,其handleEvent()方法用作回调函数。
回调函数本身具有与handleEvent()方法具有相同的参数和返回值;也就是说,回调接受一个参数:一个基于Event描述已发生事件的对象,它不返回任何内容。
例如,可以使用一个事件处理程序回调同时处理fullscreenchange和fullscreenerror,可能是如下所示:
function eventHandler(event) {
if (event.type == fullscreenchange) {
/* handle a full screen toggle */
} else /* fullscreenerror */ {
/* handle a full screen toggle error */
}
}
安全地检测选项支持部分
在旧版本的DOM规范中,第三个参数addEventListener()是一个布尔值,指示是否使用捕获。随着时间的推移,很明显需要更多的选择。不是向函数添加更多参数(在处理可选值时使事情变得非常复杂),而是将第三个参数更改为一个对象,该对象可以包含定义选项值的各种属性,以配置删除事件侦听器的过程。
因为旧版浏览器(以及一些不太旧的浏览器)仍假设第三个参数是布尔值,所以您需要构建代码以智能地处理此场景。您可以通过对您感兴趣的每个选项使用特征检测来执行此操作。
例如,如果要检查passive选项:
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;
}
这将创建一个具有该passive属性的getter函数的options对象;getter设置一个标志,passiveSupported,如果它被调用,则passiveSupported为true。这意味着如果浏览器检查对象passive上options属性的值,passiveSupported则将其设置为true;否则,它将保持为false。然后我们调用addEventListener()来设置假的事件处理程序,指定这些选项,以便在浏览器将对象识别为第三个参数时检查选项。然后,我们调用removeEventListener()给自己清理。(注意,handleEvent()在未调用的事件侦听器上会被忽略。)
您可以通过这种方式检查是否支持任何选项。只需使用类似于上面显示的代码为该选项添加一个getter。
然后,当您想要创建使用相关选项的实际事件侦听器时,您可以执行以下操作:
someElement.addEventListener("mouseup", handleMouseUp, passiveSupported
? { passive: true } : false);
这里我们为someElement元素上的mouseup事件添加一个监听器。对于第三个参数,如果passiveSupported是true,我们指定一个passive设置为true的options对象;否则,我们知道我们需要传递一个布尔值,并且我们传递false作为useCapture参数的值。
如果您愿意,可以使用Modernizr或Detect It等第三方库为您进行此测试。
示例
添加一个简单的监听器
此示例演示如何使用addEventListener()监视鼠标对元素的单击。
HTML
<table id="outside">
<tr><td id="t1">one</td></tr>
<tr><td id="t2">two</td></tr>
</table>
JavaScript
// 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()注册的click事件的侦听器。单击表格中的任何位置都会冒泡到处理程序,并运行modifyText()。
事件监听anonymous函数
在这里,我们将看看如何使用anonymous函数将参数传递给事件监听器。
HTML
<table id="outside">
<tr><td id="t1">one</td></tr>
<tr><td id="t2">two</td></tr>
</table>
JavaScript
// 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);
请注意,侦听器是一个匿名函数,它封装了代码,然后代码可以向modifyText()函数发送参数,该函数负责实际响应事件。
具有arrow功能的事件监听器
此示例演示了使用arrow函数表示法实现的简单事件侦听器。
HTML
<table id="outside">
<tr><td id="t1">one</td></tr>
<tr><td id="t2">two</td></tr>
</table>
JavaScript
// 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);
请注意,虽然anonymous和arrow函数相似,但它们具有不同的this绑定。虽然anonymous(和所有传统的JavaScript函数)创建自己的this绑定,但arrow函数继承了包含函数的this绑定。
这意味着当使用arrow函数时,包含函数可用的变量和常量也可用于事件处理程序。
选项用法示例
HTML
<div class="outer">
outer, once & none-once
<div class="middle" target="_blank">
middle, capture & none-capture
<a class="inner1" href="https://www.mozilla.org" target="_blank">
inner1, passive & preventDefault(which is not allowed)
</a>
<a class="inner2" href="https://developer.mozilla.org/" target="_blank">
inner2, none-passive & preventDefault(not open new page)
</a>
</div>
</div>
CSS
.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;
}
JavaScript
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对象中使用特定值之前,最好确保用户的浏览器支持它,因为这并非所有浏览器都支持的附加功能。
其他说明
为何使用addEventListener?
addEventListener()是注册W3C DOM中指定的事件侦听器的方法。好处如下:
- 它允许为事件添加多个处理程序。这对于AJAX库,JavaScript模块或需要与其他库/扩展很好地协作的任何其他类型的代码特别有用。
- 当监听器被激活时(捕获与冒泡),它可以让您对阶段进行更精细的控制。
- 它适用于任何DOM元素,而不仅仅是HTML元素。
注册事件侦听器的另一种旧方法如下所述。
在事件发送部分添加侦听器
如果在处理事件的过程中向EventTarget添加了一个EventListener,则该事件不会触发侦听器。但是,在事件流的后期阶段(例如冒泡阶段)可能会触发相同的侦听器。
多个相同的事件监听器
如果在有相同参数的相同EventTarget上注册了多个相同的EventListener s ,则丢弃重复的实例。它们不会导致EventListener被调用两次,并且不需要使用该removeEventListener()方法手动删除它们。但请注意,当使用anonymous函数作为处理程序时,这样的侦听器将不相同,因为anonymous函数是不相同的,即使使用简单地重复调用的SAME不变的源代码定义,即使在循环中也是如此。然而,在这种情况下重复定义相同的命名函数可能更成问题。
this处理程序部分中的值
通常需要引用触发事件处理程序的元素,例如对一组类似元素使用泛型处理程序时。
如果使用addEventListener()将处理程序函数附加到元素,则处理程序内部的this值是对元素的引用。它与传递给处理程序的event参数的currentTarget属性值相同。
如果在HTML源代码中的元素上指定了事件处理程序(例如,onclick),则属性值中的JavaScript代码将有效地包装在处理函数中,该函数以与addEventListener() 一致的方式绑定this值。this代码中出现的内容表示对元素的引用。请注意,this函数内部的值(由属性值中的代码调用)的行为与标准规则相同。这在以下示例中显示:
<table id="t" onclick="modifyText();">
. . .
带有modifyText()的this的值是对全局对象Window的引用(或者在严格模式的情况下的undefined)。
使用bind()指定this
该Function.prototype.bind()方法允许您指定应该用于对给定函数的所有调用的this值。这使您可以轻松绕过不清楚this将要发生什么的问题,具体取决于调用函数的上下文。但请注意,您需要保留对侦听器的引用,以便稍后将其删除。
这是一个有和没有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);
另一个解决方案是使用一个特殊的函数handleEvent()来捕获任何事件:
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);
处理对this的引用的另一种方法是向EventListener传递一个函数,该函数调用包含需要访问的字段的对象的方法:
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();
旧版Internet Explorer和attachEvent
在IE 9之前的Internet Explorer版本中,您必须使用attachEvent()而不是标准addEventListener()。对于IE,我们将前面的示例修改为:
if (el.addEventListener) {
el.addEventListener('click', modifyText, false);
} else if (el.attachEvent) {
el.attachEvent('onclick', modifyText);
}
attachEvent()有一个缺点:this的值将是window对象的引用,而不是引发它的元素。
该attachEvent()方法可以与onresize事件配对以检测何时调整网页中的某些元素的大小。专门的mselementresize事件与注册事件处理程序的addEventListener方法配合使用时,提供与onresize调整某些HTML元素大小时相同的功能。
兼容性
通过在脚本开头使用以下代码,您可以解决addEventListener(),removeEventListener(),Event.preventDefault(),和Event.stopPropagation()不被Internet Explorer 8支持的问题。该代码支持使用handleEvent()和DOMContentLoaded事件。
注意:useCapture不支持,因为IE 8没有任何替代方法。以下代码仅添加了IE 8支持。此IE 8 polyfill仅适用于标准模式:需要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规范引入的。在此之前,事件监听器注册如下:
// 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的话,此方法将替换元素上的现有click事件侦听器。其他事件和相关的事件处理程序(如blur(onblur)和keypress(onkeypress))的行为类似。
因为它本质上是DOM 0的一部分,所以这种添加事件监听器的技术得到了广泛的支持,并且不需要特殊的跨浏览器代码。它通常用于动态注册事件侦听器,除非需要额外的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);
}
在上面的第一种情况中,每次迭代循环都会创建一个新的(匿名)处理函数。在第二种情况下,相同的先前声明的函数被用作事件处理程序,这导致较小的内存消耗,因为只创建了一个处理函数。此外,在第一种情况下,无法调用removeEventListener(),因为不保留对anonymous函数的引用(或者这里,不保留循环可能创建的任何多个anonymous函数。);在第二种情况下,可以执行myElement.removeEventListener("click", processEvent, false) 因为processEvent是函数引用。
实际上,关于内存消耗,缺乏保留函数引用不是真正的问题;而是缺乏保持STATIC函数引用。在下面的两个问题情况中,都保留了函数引用,但由于它在每次迭代时重新定义,因此它不是静态的。在第三种情况下,每次迭代都会重新分配对anonymous函数的引用。在第四种情况下,整个函数定义是不变的,但它仍然被重复定义为new(除非它被编译器[[promote]],因此不是静态的。因此,尽管看起来只是[[Multiple identical event listeners]],但在这两种情况下,每次迭代都会创建一个新的侦听器,它具有对处理函数的唯一引用。但是,由于函数定义本身不会改变,
同样在这两种情况下,由于函数引用被保留但每次添加都重复定义,上面的remove-statement仍然可以删除一个侦听器,但现在只添加了最后一个。
// 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);
}
使用被动侦听器提高滚动性能
根据规范,该passive选项的默认值始终为false。但是,这引入了处理某些触摸事件(以及其他)的事件侦听器在尝试处理滚动时阻止浏览器的主线程的可能性,从而导致滚动处理期间性能可能大大降低。
为了避免这个问题,一些浏览器(具体而言,Chrome和Firefox)已经将文档级节点Window,Document和Document.body中的touchstart和touchmove事件的passive选项的默认值更改为true。这可以防止调用事件侦听器,因此在用户滚动时无法阻止页面呈现。
注意:如果您需要知道哪些浏览器(或这些浏览器的哪些版本)实现了这种更改的行为,请参阅下面的兼容性表。
您可以通过显式设置passiveto的值为false来覆盖此行为,如下所示:
/* 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参数的旧浏览器上,尝试使用它会阻止useCapture参数的使用,而无需正确使用特性检测。
对于基本scroll事件,您无需担心passive的值。由于无法取消,因此事件侦听器无法阻止页面呈现。
规范
规格 | 状态 | 注释 |
---|---|---|
DOM 该规范中'EventTarget.addEventListener()'的定义。 |
Living Standard
|
|
DOM4 该规范中“EventTarget.addEventListener()”的定义。 |
Obsolete
|
|
文档对象模型(DOM)级别2事件规范 该规范 中“EventTarget.addEventListener()”的定义。 |
Obsolete
|
初步定义 |
浏览器兼容性
电脑端 | 移动端 | |||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
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 参数可选 | 支持:1 | 支持 | 支持:6 | 支持:9 | 支持:11.6 | 支持 | 支持:1 | 支持:18 | 支持 | 支持:6 | 支持:11.6 | 支持 |
options对象支持的表单(第三个参数可以是Boolean,也可以是选项,以便向后兼容) | 支持:49 | 支持 | 支持:49 | 不支持号 | 支持 | 支持:10 | 支持:49 | 支持:49 | 支持 | 支持:49 | 支持 | 支持:10 |
options:capture选项 | 支持:52 | 支持 | 支持 | 不支持 | 支持 | 支持 | 支持:52 | 支持:52 | 支持 | 支持 | 支持 | 支持 |
options:once选项 | 支持:55 | 支持 | 支持:50 | 不支持 | 支持:42 | 支持 | 支持:55 | 支持:55 | 支持 | 支持:50 | 支持:42 | 支持 |
options:passive选项 | 支持:51 | 支持 | 支持 | 不支持 | 支持 | 支持 | 支持:51 | 支持:51 | 支持 | 支持 | 支持 | 支持 |
options:passive选项默认为truefortouchstart和touchmoveevents | 支持:55 | 不支持 | 支持:61 | 不支持 | ? | 不支持 | 支持:55 | 支持:55 | 不支持 | 支持:61 | ? | 不支持 |