EventTarget.addEventListener() 方法将指定的监听器注册到 EventTarget
上,当该对象触发指定的事件时,指定的回调函数就会被执行。 事件目标可以是一个文档上的元素 Document
本身,或者任何其他支持事件的对象 (比如 XMLHttpRequest
)。
语法
target.addEventListener(type, listener[, options]);
target.addEventListener(type, listener[, useCapture]);
target.addEventListener(type, listener[, useCapture, wantsUntrusted ]);
//
// Gecko/Mozilla only
type
- 表示监听事件类型的字符串。
listener
- 当所监听的事件类型触发时,会接收到一个事件通知(实现了
Event
接口的对象)对象。listener
必须是一个实现了EventListener
接口的对象,或者是一个函数 - options 可选
- 一个指定有关
listener
属性的可选参数对象。可用的选项如下:capture
:Boolean
,表示listener
会在该类型的事件捕获阶段传播到该EventTarget
时触发。once
:Boolean
,表示listener 在添加之后最多只调用一次。如果是
true,
listener
会在其被调用之后自动移除。passive
:Boolean
,表示listener
永远不会调用preventDefault()。如果 listener 仍然调用了这个函数,客户端将会忽略它并抛出一个控制台警告。
-
mozSystemGroup
: 只能在 XBL 或者是 Firefox' chrome 使用,这是个Boolean
,表示listener
被添加到 system group。
useCapture
可选Boolean
,是指在DOM树中,注册了该listener的元素,是否会先于它下方的任何事件目标,接收到该事件
。沿着DOM树向上冒泡的事件不会触发被指定为use capture(也就是设为true)的listener
。当一个元素嵌套了另一个元素,两个元素都对同一个事件注册了一个处理函数时,所发生的事件冒泡和事件捕获是两种不同的事件传播方式。事件传播模式决定了元素以哪个顺序接收事件。进一步的解释可以查看 事件流 及 JavaScript Event order 文档。 如果没有指定,useCapture
默认为 false 。
useCapture
参数值是true还是false。useCapture
仅仅在现代浏览器最近的几个版本中是可选的。 例如 Firefox 6以前的版本都不是可选的。为了能够提供更广泛的支持,你应该提供这个参数。wantsUntrusted
- 如果为
true
, 则事件处理程序会接收网页自定义的事件。此参数只适用于 Gecko,主要用于附加组件的代码和浏览器本身。请见 Interaction between privileged and non-privileged pages.
示例
添加一个简单的 listener
<table id="outside"> <tr><td id="t1">one</td></tr> <tr><td id="t2">two</td></tr> </table>
// 改变t2的函数 function modifyText() { var t2 = document.getElementById("t2"); if (t2.firstChild.nodeValue == "three") { t2.firstChild.nodeValue = "two"; } else { t2.firstChild.nodeValue = "three"; } } // 为table添加事件监听器 var el = document.getElementById("outside"); el.addEventListener("click", modifyText, false);
在上个例子中,modifyText() 是一个click 类型的 listener
,通过使用addEventListenter注册到table对象上。在表格中任何位置单击都会触动事件并执行modifyText()。
如果你想传参到 listener
中,你可以使用匿名函数。
带有匿名函数的 listener
<table id="outside"> <tr><td id="t1">one</td></tr> <tr><td id="t2">two</td></tr> </table>
// 改变t2值的函数 function modifyText(new_text) { var t2 = document.getElementById("t2"); t2.firstChild.nodeValue = new_text; } // 为table对象添加事件监听器 var el = document.getElementById("outside"); el.addEventListener("click", function(){modifyText("four")}, false);
备注
为什么要使用 addEventListener
?
addEventListener
是 W3C DOM 规范中提供的注册事件监听器的方法。它的优点包括:
- 它允许给一个事件注册多个
listener
。当存在其他的库时,使用 DHTML 库或者 Mozilla extensions 不会出现问题。 - 它提供了一种更精细的手段控制
listener
的触发阶段。(即可以选择捕获或者冒泡)。 - 它对任何 DOM 元素都是有效的,而不仅仅只对 HTML 元素有效。
除了这种方法以外,后文会简单阐述一些注册 listener
的旧方法 。
在事件分派时添加事件处理器
当一个 EventListener
在 EventTarget
正在处理事件的时候被注册到 EventTarget
上,它不会被立即触发,但可能在事件流后面的事件触发阶段被触发,例如可能在捕获阶段添加,然后在冒泡阶段被触发。
多个相同的事件处理器
同一个 EventTarget 注册了多个相同的 EventListener
,那么重复的实例会被抛弃。所以这么做不会使得 EventListener
被调用两次,也不需要用 removeEventListener 手动清除多余的EventListener
,因为重复的都被自动抛弃了。
处理过程中 this
的值的问题
通常来说this的值是触发事件的元素的引用,这种特性在多个相似的元素使用同一个通用事件监听器时非常让人满意。
当使用 addEventListener()
为一个元素注册事件的时候,句柄里的 this 值是该元素的引用。其与传递给句柄的 event 参数的 currentTarget 属性的值一样。
If an event attribute (e.g., onclick
) is specified on an element in the HTML source, the JavaScript code in the attribute value is effectively wrapped in a handler function that binds the value of this
in a manner consistent with the use ofaddEventListener()
; an occurrence of this
within the code represents a reference to the element. Note that the value of this
inside a function called by the code in the attribute value behaves as per standard rules. 比如下面的例子:
<table id="t" onclick="modifyText();"> . . .
这时modifyText()
中的this
的值会变成全局 (window) 对象的引用(在严格模式中为 undefined)
。
Function.prototype.bind()
方法,允许制定函数调用时的 this
的值。这使得想要绕开由于调用情况不同,this 取值不同的问题变得十分容易 。然而请注意,你应该保留一个 listener 的
引用,以便在未来需要的时候能够比较好地移除。下面是 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);
上面这个例子的一个问题是不可能移除使用了 bind
的 listener。一种解决办法是使用定制的函数'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);
Another way of handling the reference to this, is to pass to the EventListener a function, calling the method of the object which fields want to be accessed:
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 方法
对于 Internet Explorer 来说,在IE 9之前,你必须使用 attachEvent
而不是使用标准方法 addEventListener
。为了支持IE,前面的例子需要改成这样:
if (el.addEventListener) { el.addEventListener('click', modifyText, false); } else if (el.attachEvent) { el.attachEvent('onclick', modifyText); }
使用 attachEvent
方法有个缺点,this
的值会变成 window
对象的引用而不是触发事件的元素。
兼容性
You can work around the addEventListener
, removeEventListener
, Event.preventDefault
and Event.stopPropagation
not being supported by IE 8 using the following code at the beginning of your script. The code supports the use of handleEvent
and also the DOMContentLoaded
event.
(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; } } })();
注册 listener
的旧方法
addEventListener()
在DOM 2 Events 规范中引入。在这之前,事件监听器应该用以下的方法注册:
// Pass 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 ... };
这个方法会替换这个元素上所有已存在的 onclick
事件。对于其他事件是类似的,比如 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}); }
在第一种情况下,每个循环中都会创建一个新的(匿名)函数。在第二种情况下,会使用先前声明的相同的函数作为事件处理器。这样的结果是占用的存储空间更小。而且,在第一种情况中,由于没有保持到匿名函数的引用,它不可能被调用 element.removeEventListener
,这是因为我们没有一个可参考的处理器,而在第二种情况,它可以被 myElement.removeEventListener("click", processEvent, false)
。
使用 passive 改善的滚屏性能
var elem = document.getElementById('elem');
elem.addEventListener('touchmove
', function listener() { /* do something */ }, { passive: true });
This way a touchmove
listener will not block while a user is scrolling (same applies to wheel
events). Demo availablehere (Google Developers Page).
Beware: Browsers who do not support event listener options will see the 3rd argument as useCapture
and therefore as true
.
规范
Specification | Status | Comment |
---|---|---|
DOM EventTarget.addEventListener() |
Living Standard | |
DOM4 EventTarget.addEventListener() |
Obsolete | |
Document Object Model (DOM) Level 2 Events Specification EventTarget.addEventListener() |
Obsolete | Initial definition |
浏览器兼容性
Feature | Chrome | Firefox (Gecko) | Internet Explorer | Opera | Safari (WebKit) |
---|---|---|---|---|---|
Basic support | 1.0[1][2] | 1.0 (1.7 or earlier)[3] | 9.0[4] | 7 | 1.0[1] |
useCapture made optional |
1.0 | 6 (6) | 9.0 | 11.60 | (Yes) |
options parameter (with capture and passive values)[5] |
49.0 ( |
49 (49) | 未实现 | 未实现 | Landed in Nightly WebKit bug 158601 |
once value in the options parameter |
55 | 50 (50) | 未实现 | 未实现 | Landed in Nightly WebKit bug 149466 |
Feature | Android | Android Webview | Firefox Mobile (Gecko) | IE Mobile | Opera Mobile | Safari Mobile | Chrome for Android |
---|---|---|---|---|---|---|---|
Basic support | 1.0 |
(Yes)[2] |
1.0 (1.0)[3] | 9.0 | 6.0 | 1.0[1] |
(Yes)[2] |
useCapture made optional |
? |
(Yes) |
6.0 (6) | ? | ? | ? |
(Yes) |
options parameter (with capture and passive values)[5] |
未实现 | 49.0 (capture ) 51.0 (passive ) |
49.0 (49) | ? | ? | ? | 49.0 (capture ) 51.0 (passive ) |
once value in the options parameter |
未实现 | 未实现 | 50 (50) | 未实现 | 未实现 |
[1] 尽管 WebKit 在2011六月前后显式地给 useCapture
参数添加了[optional]
标记,但是在这之前它一直有效。这项改变在Safari 5.1 及 Chrome 13 版本落地。
[2] Before Chrome 49, the type and listener parameters were optional.
[3] 在Firefox 6之前,如果useCapture
没有显式指定为false,浏览器可能抛出一个异常。在Gecko 9.0 (Firefox 9.0 / Thunderbird 9.0 / SeaMonkey 2.6)之前,如果listener
参数是null,使用addEventListener()
会抛出一个异常,而现在这样做方法会正常返回,但不会有任何效果。
[4] Older versions of Internet Explorer support the proprietary EventTarget.attachEvent
method instead.
[5] For backwards compatibility, browsers that support options
allow the third parameter to be either options
or Boolean
.