添加和删除带参数的事件监听器

28
我是一款纯JavaScript工具的编写者,当启用时,会为传入其中的每个元素添加事件监听器。 我想做到这样:
var do_something = function (obj) {
        // do something
    };

for (var i = 0; i < arr.length; i++) {
    arr[i].el.addEventListener('click', do_something(arr[i]));
}
很遗憾,这并不起作用,因为据我所知,添加事件监听器时,只能将参数传递到匿名函数中
for (var i = 0; i < arr.length; i++) {
    arr[i].el.addEventListener('click', function (arr[i]) {
        // do something
    });
}
问题在于当工具被禁用时,我需要能够删除事件监听器,但我认为使用匿名函数无法删除事件监听器。
for (var i = 0; i < arr.length; i++) {
    arr[i].el.removeEventListener('click', do_something);
}

我知道我可以轻松使用jQuery来解决我的问题,但我正在尝试最小化依赖关系。jQuery一定有办法解决这个问题,但代码有点混乱!


1
给你的监听器命名,这样你就可以使用 removeEventListener - marekful
1
既然您知道jQuery可以解决您的问题,那么您只需要阅读源代码了解它是如何工作的。 - pktangyue
3
至少为加粗 "vanilla JavaScript" 和斜体强调关于 jQuery 的观点给你点赞!(希望现在没有人会提出jQuery作为解决方案了...)但同时也要再点赞,因为这是一个好问题。 - guypursey
7个回答

29

这是无效的:

arr[i].el.addEventListener('click', do_something(arr[i]));

监听器必须是一个函数引用。当您将函数作为addEventListener的参数调用时,该函数的返回值将被视为事件处理程序。在分配监听器时无法指定参数。处理程序函数将始终使用传递的event作为第一个参数进行调用。要传递其他参数,可以将处理程序包装到匿名事件侦听器函数中,如下所示:

elem.addEventListener('click', function(event) {
  do_something( ... )
}

如果要通过removeEventListener移除事件,只需要命名处理函数即可:

function myListener(event) {
  do_something( ... );
}

elem.addEventListener('click', myListener);

// ...

elem.removeEventListener('click', myListener);
为了在处理函数中访问其他变量,您可以使用闭包。例如:
function someFunc() {
  var a = 1,
      b = 2;

  function myListener(event) {
    do_something(a, b);
  }
  
  elem.addEventListener('click', myListener);
}

1
当我不需要移除监听器或在与someFunc()相同的作用域中移除它时,这是可以的。不幸的是,情况并非如此(尽管也许应该是这样?) - tomturton
通过将监听器函数设置为全局变量或者在你可以访问到的上下文中,这个问题就可以解决了。例如:window.listeners.myListener = function() { ... }。这样你随时都可以使用 elem.removeEventListener('click', window.listeners.myListener) - marekful
我通过将监听函数应用于arr[i]来解决了这个问题。然后,我可以引用整个arr[i]对象,而无需向监听器传递参数。 - tomturton

3
// Define a wrapping function
function wrappingFunction(e) {
  // Call the real function, using parameters
  functionWithParameters(e.target, ' Nice!')
}
// Add the listener for a wrapping function, with no parameters
element.addEventListener('click', wrappingFunction);
// Save a reference to the listener as an attribute for later use
element.cleanUpMyListener = ()=>{element.removeEventListener('click', wrappingFunction);}

// ...

element.cleanUpMyListener ()

步骤1) 给你的函数命名。

步骤2) 保存对你的函数的引用(在这种情况下,将引用保存为元素本身的属性)

步骤3) 使用函数引用来移除监听器。

// Because this function requires parameters, we need this solution
function addText(element, text) {
  element.innerHTML += text
}

// Add the listener
function addListener() {
  let element = document.querySelector('div')
  if (element.removeHoverEventListener){
    // If there is already a listener, remove it so we don't have 2
    element.removeHoverEventListener()
  }
  // Name the wrapping function
  function hoverDiv(e) {
      // Call the real function, using parameters
      addText(e.target, ' Nice!')
  }
  // When the event is fired, call the wrapping function
  element.addEventListener('click', hoverDiv);
  // Save a reference to the wrapping function as an attribute for later use
  element.removeHoverEventListener = ()=>{element.removeEventListener('click', hoverDiv);}
}

// Remove the listener
function removeListener() {
  let element = document.querySelector('div')
  if (element.removeHoverEventListener){
    // Use the reference saved before to remove the wrapping function
    element.removeHoverEventListener()
  }
}
<button onclick="addListener()">Turn Listener on</button>
<button onclick="removeListener()">Turn Listener off</button>
<div>Click me to test the event listener.</div>


2

为了将参数传递给事件处理程序,可以使用bind或者返回函数的处理程序

// using bind
var do_something = function (obj) {
  // do something
}

for (var i = 0; i < arr.length; i++) {
  arr[i].el.addEventListener('click', do_something.bind(this, arr[i]))
}


// using returning function
var do_something = obj => e {
  // do something
}

for (var i = 0; i < arr.length; i++) {
  arr[i].el.addEventListener('click', do_something(arr[i]))
}

但是在这两种情况下,要删除事件处理程序是不可能的,因为bind会给出一个新的引用函数,而返回函数也会在每次执行循环时返回一个新函数。

为了解决这个问题,我们需要将函数的引用存储在一个Array中,并从中删除。

// using bind
var do_something = function (obj) {
  // do something
}
var handlers = []

for (var i = 0; i < arr.length; i++) {
  const wrappedFunc = do_something.bind(this, arr[i])
  handlers.push(wrappedFunc)
  arr[i].el.addEventListener('click', wrappedFunc);
}
//removing handlers
function removeHandlers() {
  for (var i = 0; i < arr.length; i++) {
    arr[i].el.removeEventListener('click', handlers[i]);
  }
  handlers = []
}

不幸的是,e.currentTarget 实际上会指向 arr[i].el。 - tomturton
1
你在问题中发布了匿名函数arr[i].el.addEventListener('click', function (arr[i])的第二个代码片段,所以你想传递什么参数呢? - Sandeep
参数需要是 arr[i],而不是侦听器附加到的 arr[i].el - tomturton
如果您想将arr[i]传递给侦听器所附加的对象,则默认情况下传递给函数的event包含arr[i] - Sandeep
1
@Roko 谢谢你的鼓励 :) - Sandeep
显示剩余2条评论

0

对我有效的方法:

我需要在循环中为HTML元素添加事件监听器,并给处理函数提供一个唯一的参数,然后我希望能够移除这些监听器。在我的情况下,这个参数是元素的索引。我创建了一个数组来存储这些函数,然后可以根据需要从数组中移除监听器。

在下面的示例中,如果打开事件监听器,您可以使用鼠标拖动来高亮多行。您可以使用“移除事件监听器”按钮禁用高亮功能。您还可以在事件处理程序onMouseDownonMouseUp中访问事件和行的索引。

removeEventListeners函数所示,移除监听器非常简单。

var mouseDownListeners = [];
var mouseUpListeners = [];
var selStart = null;
var selEnd = null;

document.getElementById('button1').addEventListener('click', () => addEventListeners());
document.getElementById('button2').addEventListener('click', () => removeEventListeners());

function addEventListeners() {
  removeEventListeners();
  let rows = getRows();

  rows.forEach((row, i) => {
    let mouseUpListener = function(e) {
      onMouseUp.bind(null, e, i)();
    }

    let mouseDownListener = function(e) {
      onMouseDown.bind(null, e, i)();
    }

    mouseUpListeners[i] = mouseUpListener;
    mouseDownListeners[i] = mouseDownListener;

    row.addEventListener('mouseup', mouseUpListener);
    row.addEventListener('mousedown', mouseDownListener);
  })
}

function removeEventListeners() {
  let rows = getRows();

  rows.forEach((row, i) => {
    row.removeEventListener('mouseup', mouseUpListeners[i]);
    row.removeEventListener('mousedown', mouseDownListeners[i]);
  })
  mouseUpListeners = [];
  mouseDownListeners = [];
}

function getRows() {
  let rows = document.querySelectorAll('div.row');
  return rows;
}

var onMouseDown = function(e, i) {
  selStart = i;
}

var onMouseUp = function(e, i) {
  selEnd = i;

  assignClasses(selStart, selEnd);

  selStart = null;
  selEnd = null;
}

function assignClasses(start, end) {
  let rows = getRows();
  rows.forEach((row, i) => {
    if (start <= i && i <= end) {
      row.classList.add('highlighted-row');
    } else {
      row.classList.remove('highlighted-row');
    }
  })
}
.row {
  border: 1px solid black;
  user-select: none; // chrome and Opera
  -moz-user-select: none; // Firefox
  -webkit-text-select: none; // IOS Safari
  -webkit-user-select: none; // Safari
}

.highlighted-row {
  background-color: lightyellow;
}
<div>
  <div>
    <button id='button1'>Add listeners</button>
    <button id='button2'>Remove listeners</button>
  </div>

  <div>
    <div class='row'>Row1</div>
    <div class='row'>Row2</div>
    <div class='row'>Row3</div>
    <div class='row'>Row4</div>
    <div class='row'>Row5</div>
    <div class='row'>Row6</div>
    <div class='row'>Row7</div>
    <div class='row'>Row8</div>
    <div class='row'>Row9</div>
    <div class='row'>Row10</div>
  </div>
  <div>


0

也许这不是完美的解决方案,但接近理想,此外我没有看到其他方法。

感谢Kostas Bariotis

关键在于解决方案:

那么当我们需要在运行时某个时刻删除已附加的事件处理程序时该怎么办呢?遇见handleEvent,JavaScript在查找已附加到事件的处理程序时寻找的默认函数。

如果链接失效(我放置了第一种方式)

let Button = function () {

  this.el = document.createElement('button');
  this.addEvents();
}

Button.prototype.addEvents = function () {
  this.el.addEventListener('click', this);
}

Button.prototype.removeEvents = function () {
  this.el.removeEventListener('click', this);
}

Button.prototype.handleEvent = function (e) {
  switch(e.type) {
    case 'click': {
     this.clickHandler(e);
    }
  }
}

Button.prototype.clickHandler = function () {
  /* do something with this */
}

P.S:

JS类实现中的相同技巧。

如果您使用TypeScript开发,则必须从EventListenerObject接口实现handleEvent方法。


0

这可以很容易地完成,只是不像你现在所做的那样。

与其尝试添加和删除随机匿名函数,你需要添加或删除一个处理执行你的其他函数的函数。

var
    // Here we are going to save references to our events to execute
    cache = {},

    // Create a unique string to mark our elements with
    expando = String( Math.random() ).split( '.' )[ 1 ],

    // Global unique ID; we use this to keep track of what events to fire on what elements
    guid = 1,

    // The function to add or remove. We use this to handler all of other 
    handler = function ( event ) {

        // Grab the list of functions to fire
        var handlers = ( cache[ this[ expando ] ] && cache[ this[ expando ] ][ event.type ] ) || false;

        // Make sure the list of functions we have is valid
        if ( !handlers || !handlers.length ) {
            return;
        }

        // Iterate over our individual handlers and call them as we go. Make sure we remeber to pass in the event Object
        handlers.forEach( function ( handler ) {
            handler.call( this, event );
        });

    },

    // If we want to add an event to an element, we use this function
    add = function ( element, type, fn ) {

        // We test if an element already has a guid assigned
        if ( !element[ expando ] ) {
            element[ expando ] = guid++;
        }

        // Grab the guid number
        var id = element[ expando ];

        // Make sure the element exists in our global cache
        cache[ id ] = cache[ id ] || {};

        // Grab the Array that we are going to store our handles in
        var handlers = cache[id ][ type ] = cache[ id ][ type ] || [];

       // Make sure the handle that was passed in is actually a function
        if ( typeof fn === 'function' ) {
            handlers.push( fn );
        }

        // Bind our master handler function to the element
        element.addEventListener( type, handler, false );

    };

// Add a click event to the body element
add( document.body, 'click', function ( event ) {
    console.log( 1 );
});

这只是我之前写过的内容的简化版本,但我希望你能理解要点。


-2
使用以下代码,您可以带有一些参数来添加 'addEventListener':

addEventListener

{
   myButton.addEventListener("click",myFunction.bind(null,event,myParameter1,myParameter2)); 
}

函数'myFunction'应该像这样:

{
   function myFunction(event, para1, para2){...}
}

2
bind会改变函数引用,因此删除命名函数的事件监听器将失败。 - Craveiro
1
这是完全不正确的答案,并且没有提供任何方法来removeEventListener - 与@Craveiro指出失败相反 - Roko C. Buljan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,