避免重复addEventListener的核心就是在添加前通过removeEventListener将已经添加的处理函数进行移除。如下代码为id=btn的元素添加click事件的处理函数clickHandler:

const $btn = document.getElementById('btn');
function clickHandler() {
    console.info(`this is in clickHandler`);
$btn.removeEventListener('click', clickHandler);
$btn.addEventListener('click', clickHandler);

但是在这里涉及到一个问题,JavaScript中函数是引用类型,因此在进行removeEventListener时,第二个参数需要和addEventListener时的引用相同,否则无法达到移除的效果。如下代码所示:

Html部分代码如下

<h1 id="btn">click here</h1> <section> <button onclick="addListener()" id="btnForBtn">add handler for click here</button> </section>

JavaScript代码如下

const $btn = document.getElementById('btn');
let count = 0;
function addListener() {
    function clickHandler() {
        console.info(`this is in clickHandler but created ${++count} times`);
    $btn.removeEventListener('click', clickHandler);
    $btn.addEventListener('click', clickHandler);

点击add handler to click here按钮为click here按钮添加点击事件,并在添加前进行移除,希望达到唯一处理函数的效果。右侧输出展示的是六次add handler to click here按钮点击,一次click here按钮点击的效果。可以看到,并未达到预期。原因是:每次执行函数addListener都重新创建了clickHandler函数,因此在进行removeEventListener时并未将原有的处理函数进行移除。

如果将clickHandler移动到addListener函数之外仅进行一次定义,那么是可以达到唯一添加的效果,但是在有些业务需求中需要进行如此类代码结构的编写方式(如Vue中,在directive的各生命周期中进行事件绑定)。而此时为了达到唯一绑定的效果,其实就是保存clickHandler的唯一引用的问题。如果不能提取到外部作为全局变量,那么换个思路,作为待绑定元素的属性也就可以了,JavaScript代码如下:

const $btn = document.getElementById('btn');
let count = 0;
function addListener() {
    if ($btn.clickHandler) {
        $btn.removeEventListener('click', $btn.clickHandler);
    $btn.clickHandler = () => {
        console.info(`this is in clickHandler but created ${++count} times`);
    $btn.addEventListener('click', $btn.clickHandler);

这里需要注意的是:

过多的在元素上绑定属性,有可能会造成性能的损耗和增加维护的成本
clickHandler属性的定义应该进行命名空间的限制,以避免发生同名属性覆盖的问题
最后需要提到的是,对同一个元素的同一个事件重复进行处理函数的添加,是只生效一次的,如下代码中:

const $btn = document.getElementById('btn');
const clickHandler = () => {
    console.info('this is handler1')
// 多次添加同一个事件处理函数,则不会重复执行
$btn.addEventListener('click', clickHandler);
$btn.addEventListener('click', clickHandler);

在页面中对$btn元素进行点击,clickHandler将只执行一次。

==================

实战:plus会员中战略banner解决绑定多个addEventListener,把原来绑定到函数上改为绑定到属性上

bindEvents() {
    const activity = this.isNewFormalSub ? document.getElementById('activitySub') : document.getElementById('activity');
    if (activity) {
        [].forEach.call(activity.querySelectorAll('.swiper-slide'), item => {
            if (item.clickHandler) {
                item.removeEventListener('click', item.clickHandler);
            item.clickHandler = () => {
                console.log('触发点击');
                const ins = item.getAttribute('data-ins');
                this.openLink(this.bannerData[ins], parseInt(ins));
            item.addEventListener('click', item.clickHandler);