EventTarget方法:addEventListener()

2019-01-19 10:02 更新

该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" rel="external nofollow" target="_blank"  target="_blank">
            inner1, passive & preventDefault(which is not allowed)
        </a>
        <a class="inner2" href="https://developer.mozilla.org/" rel="external nofollow" target="_blank"  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 不支持
以上内容是否对您有帮助:
在线笔记
App下载
App下载

扫描二维码

下载编程狮App

公众号
微信公众号

编程狮公众号