Rebind JavaScript events and addEventListener fires twice

孤者浪人 提交于 2019-12-25 09:15:18

问题


I have a class method which defines the event listeners. Let us use the following code snippet for simplicity.

function bindEvents() {
    document.querySelector('button').addEventListener('click', (e) => {
        console.log('clicked!');
    }); 
}

// Initial event binding
bindEvents();

// Rebind events at some point for dynamically created elements
bindEvents();
<button type="button">Click</button>

Everything works fine when using bindEvents() only once, however for example calling it again in ajax callback results in listener executed twice. So this means after second bindEvents(), clicking the button will console.log() twice and so on. Is there a way I can get around this behavior?

I know I can bind the events "dynamically" on the document and check with e.target, but there is a situation where I need mouseenter/mouseleave events and I don't think it's a good idea to always have those eventListeners on the document.

I've read somewhere the following, but it seems false...

The .addEventListener method ensures that the same function reference won't be bound more than once to the same element/event/captures combination.

Also I have played with the options parameter from here https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener, but without success.

Excuse me if this was answered somewhere, but I failed to find answer in SO and the search engines.

UPDATE: Is there a way to overwrite existing eventListeners or old ones should be removed with removeEventListener like kcp suggested below? Is there more elegant solution to this problem at all?


回答1:


The .addEventListener method ensures that the same function reference won't be bound more than once to the same element/event/captures combination.

In your case, each time you execute bindEvents() a completely new handler is passed to the click event listener since you define new function (no matter it looks the same, it is different object). To use the same handler each time you must define it outside bindEvents and pass it by name (by reference). This works as expexted:

function clickHandler(e){
  alert('clicked!');
}

function bindEvents() {
    document.querySelector('button').addEventListener('click', clickHandler); 
}

// Initial event binding
bindEvents();

// Rebind events at some point for dynamically created elements
bindEvents();
<button>click</button>

However with jQuery I use the following approach which allows me to specify that only elements in a specific container (context or ctx) will be bound:

$.fn.bindEvents = function bindEvents(ctx){

	ctx = ctx || this;

	$('button', ctx).on('click', function(event){
		alert(1);
	});
};

$('body').bindEvents();

$('div').bindEvents();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<button>Click</button>

<div><button>in div </button></div>

In the example above bindEvents is executed twice but the first button is bound only once since it's not in a div. While if you click on the second button it alerts twice because satisfies both contexts.




回答2:


addEventListener does not overwrite existing event listeners, it simply adds a new one as the method name implies. Existing listeners must be removed using the removeEventListener method.

function onClick($event) {
    console.log('clicked!');
}
function bindEvents() {
    /** Remove event listener first **/
    document.querySelector('button').removeEventListener('click', onClick);
    document.querySelector('button').addEventListener('click', onClick); 
}

removeEventListener docs




回答3:


Apart from removeEventListener, You can also use Event delegation. Using this mechanism event is handler by attaching event listener to parent element.

var elem = document.querySelector('div');

elem.addEventListener('click', function(e) {
  e = e || event
  var target = e.target;
  if (target.nodeName != 'BUTTON')
    return;

  console.log('clicked ' + target.textContent);
});

//Simulate addition of dynamic elements
setTimeout(function() {
  var newButton = document.createElement('button');
  newButton.innerHTML = 'Click 2';
  elem.appendChild(newButton)
}, 2000)
<div>
  <button type="button">Click</button>
</div>


来源:https://stackoverflow.com/questions/41720943/rebind-javascript-events-and-addeventlistener-fires-twice

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!