EventTarget.addEventListener() 方法将指定的监听器注册到 EventTarget 上,当该对象触发指定的事件时,指定的回调函数就会被执行。 事件目标可以是一个文档上的元素 Document 本身,或者任何其他支持事件的对象 (比如 XMLHttpRequest)

语法

target.addEventListener(typelistener[, 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 thisinside a function called by the code in the attribute value behaves as per standard rules. 比如下面的例子:

<table id="t" onclick="modifyText();">
. . .

这时modifyText()中的this 的值会变成全局 (window) 对象的引用(在严格模式中为 undefined)

注意: JavaScript 1.8.5 引入了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。一种解决办法是使用定制的函数去捕获任意类型:

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);

传统的 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 addEventListenerremoveEventListenerEvent.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.

Note: IE8 不具有任何替代 useCapture 的方法,useCapture 是 IE8 不支持的。 请注意下面的代码只能添加 IE8。另外请注意,下面这个 IE8 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;
    }
  }
})();

注册 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()
Recommendation  
Document Object Model (DOM) Level 2 Events Specification
EventTarget.addEventListener()
Recommendation 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 (capture) 51.0 (passive)

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.

相关链接

文档标签和贡献者

标签: 
 最后编辑者: xgqfrms-GitHub,