如何检查动态附加的事件监听器是否存在?

223

我的问题是:有没有办法检查动态附加的事件监听器是否存在?或者如何检查DOM中“onclick”属性的状态?我已经像在Stack Overflow上搜索解决方案,但没有找到。这是我的HTML:

<a id="link1" onclick="linkclick(event)"> link 1 </a>
<a id="link2"> link 2 </a> <!-- without inline onclick handler -->

然后在JavaScript中,我将一个动态创建的事件监听器附加到第二个链接:

document.getElementById('link2').addEventListener('click', linkclick, false);
代码可以正常运行,但是我的所有尝试都无法检测到已连接的侦听器:
// test for #link2 - dynamically created eventlistener
alert(elem.onclick); // null
alert(elem.hasAttribute('onclick')); // false
alert(elem.click); // function click(){[native code]} // btw, what's this?

这里有jsFiddle代码。 如果你点击“为2添加onclick”然后点击“[link 2]”,事件就会触发, 但是“Test link 2”始终返回false。有人能帮忙吗?


2
很抱歉,但是使用您当前的方法无法获取事件绑定:https://dev59.com/61XTa4cB1Zd3GeqPzDDn - Ivan
3
你可以使用 Chrome 开发者工具来完成这个任务:https://dev59.com/vF4c5IYBdhLWcg3wUI4N#41137585。 - Alfian Busyro
1
你可以通过创建自己的存储来跟踪监听器来实现。更多信息请参见我的答案 - Angel Politis
1
如果目标是防止已经添加的事件再次被添加,那么正确的答案在这里:https://dev59.com/vF4c5IYBdhLWcg3wUI4N#47337711。基本上,如果你使用一个命名函数而不是匿名函数,重复的相同事件监听器将被丢弃,你就不需要担心它们了。 - Syknapse
1
如果您担心有重复的监听器,请在再次添加之前删除当前事件监听器。这并不完美,但很简单。 - Jacksonkr
24个回答

125

我做了类似的事情:

const element = document.getElementById('div');

if (element.getAttribute('listener') !== 'true') {
     element.addEventListener('click', function (e) {
         const elementClicked = e.target;
         elementClicked.setAttribute('listener', 'true');
         console.log('event has been attached');
    });
}

在附加侦听器时,为元素创建一个特殊属性,然后检查它是否存在。


16
过去我使用过的方法是这样的。我的建议是使用非常具体的语法结构。例如,不要使用"listener",而是使用事件本身。因此,使用"data-event-click"。这提供了一些灵活性,以便在您想要执行多个事件时使用,并使事情更加易读。 - conrad10781
2
这个加了@conrad10781的部分似乎是最可靠和最简单的方法。如果由于任何原因元素被重新渲染并且事件侦听器断开连接,那么这个属性也将被重置。 - Arye Eidelman
28
我可能有点疯了(此帖获得74个赞),但我认为应该在addEventListender函数之外添加elementClicked.setAttribute('listener', 'true'); - 否则只有在事件触发时才会添加它,因此实际上不能可靠地检查事件侦听器是否已连接。 - Dazed
在发布时,这是最高投票的,但逻辑有些不对。下面有一个更好的替代方案(https://dev59.com/z2gu5IYBdhLWcg3wRk9h#69189193)。 - Dazed

77

无法检查动态附加的事件监听器是否存在。

唯一的方法是像这样附加事件监听器来查看是否已经附加了事件监听器:

elem.onclick = function () { console.log (1) }

你可以通过返回!!elem.onclick (或类似的内容) 来测试是否已将事件监听器附加到 onclick


33

我会创建一个布尔变量放在函数外部,初始值为 FALSE, 当你添加事件时将其设置为 TRUE。这将成为一个标志,以便在再次添加事件之前进行判断。以下是示例代码:

// initial load
var attached = false;

// this will only execute code once
doSomething = function()
{
 if (!attached)
 {
  attached = true;
  //code
 }
} 

//attach your function with change event
window.onload = function()
{
 var txtbox = document.getElementById('textboxID');

 if (window.addEventListener)
 {
  txtbox.addEventListener('change', doSomething, false);
 }
 else if(window.attachEvent)
 {
  txtbox.attachEvent('onchange', doSomething);
 }
}

1
这需要您更改附加事件的代码,以跟踪它是否已附加(类似于this此答案)。如果您无法控制代码,则无法正常工作。 - user202729
1
在我的使用情况中,用户可能会在不同的选项卡中打开相同的页面。这仅在事件仅附加在一个选项卡的一个位置时才有效。布尔值无法真正与事件是否已附加保持同步,因为我们只是猜测它在加载时没有被附加。 - ceckenrode

15

2
更准确地说,在这里:https://dev59.com/vF4c5IYBdhLWcg3wUI4N#41137585。 - JohnK
那个问题比这个问题新得多。 - undefined

8

tl;dr: 不,你不能以任何本地支持的方式完成这个任务。


我知道的实现此操作的唯一方法是创建一个自定义存储对象,用于保存添加的监听器记录。具体做法如下:

/* Create a storage object. */
var CustomEventStorage = [];

步骤1:首先,您需要一个函数,该函数可以遍历存储对象并返回给定元素的记录(或false)。

/* The function that finds a record in the storage by a given element. */
function findRecordByElement (element) {
    /* Iterate over every entry in the storage object. */
    for (var index = 0, length = CustomEventStorage.length; index < length; index++) {
        /* Cache the record. */
        var record = CustomEventStorage[index];

        /* Check whether the given element exists. */
        if (element == record.element) {
            /* Return the record. */
            return record;
        }
    }

    /* Return false by default. */
    return false;
}

步骤2: 接下来,您需要一个函数,它可以添加事件监听器,同时将监听器插入到存储对象中。

/* The function that adds an event listener, while storing it in the storage object. */
function insertListener (element, event, listener, options) {
    /* Use the element given to retrieve the record. */
    var record = findRecordByElement(element);

    /* Check whether any record was found. */
    if (record) {
        /* Normalise the event of the listeners object, in case it doesn't exist. */
        record.listeners[event] = record.listeners[event] || [];
    }
    else {
        /* Create an object to insert into the storage object. */
        record = {
            element: element,
            listeners: {}
        };

        /* Create an array for event in the record. */
        record.listeners[event] = [];

        /* Insert the record in the storage. */
        CustomEventStorage.push(record);
    }

    /* Insert the listener to the event array. */
    record.listeners[event].push(listener);

    /* Add the event listener to the element. */
    element.addEventListener(event, listener, options);
}

第三步:关于您问题的实际需求,您需要以下函数来检查元素是否已经添加了指定事件的监听器。

/* The function that checks whether an event listener is set for a given event. */
function listenerExists (element, event, listener) {
    /* Use the element given to retrieve the record. */
    var record = findRecordByElement(element);

    /* Check whether a record was found & if an event array exists for the given event. */
    if (record && event in record.listeners) {
        /* Return whether the given listener exists. */
        return !!~record.listeners[event].indexOf(listener);
    }

    /* Return false by default. */
    return false;
}
第四步:最后,您需要一个能够从存储对象中删除监听器的函数。
/* The function that removes a listener from a given element & its storage record. */
function removeListener (element, event, listener, options) {
    /* Use the element given to retrieve the record. */
    var record = findRecordByElement(element);

    /* Check whether any record was found and, if found, whether the event exists. */
    if (record && event in record.listeners) {
        /* Cache the index of the listener inside the event array. */
        var index = record.listeners[event].indexOf(listener);

        /* Check whether listener is not -1. */
        if (~index) {
            /* Delete the listener from the event array. */
            record.listeners[event].splice(index, 1);
        }

        /* Check whether the event array is empty or not. */
        if (!record.listeners[event].length) {
            /* Delete the event array. */
            delete record.listeners[event];
        }
    }

    /* Add the event listener to the element. */
    element.removeEventListener(event, listener, options);
}

片段:

window.onload = function () {
  var
    /* Cache the test element. */
    element = document.getElementById("test"),

    /* Create an event listener. */
    listener = function (e) {
      console.log(e.type + "triggered!");
    };

  /* Insert the listener to the element. */
  insertListener(element, "mouseover", listener);

  /* Log whether the listener exists. */
  console.log(listenerExists(element, "mouseover", listener));

  /* Remove the listener from the element. */
  removeListener(element, "mouseover", listener);

  /* Log whether the listener exists. */
  console.log(listenerExists(element, "mouseover", listener));
};
<!-- Include the Custom Event Storage file -->
<script src = "https://cdn.rawgit.com/angelpolitis/custom-event-storage/master/main.js"></script>

<!-- A Test HTML element -->
<div id = "test" style = "background:#000; height:50px; width: 50px"></div>


虽然距离原帖发布已经过去了5年,但我相信未来遇到这个问题的人会从这个答案中受益,所以请随意提出建议或改进。


1
谢谢您提供的解决方案,它简单而且稳定。不过有一点需要指出,代码中有一个小错误。函数“removeListener”检查事件数组是否为空,但三元运算符是不正确的。应该改为“if (record.listeners[event].length != -1)”。 - JPA

8

这个方法不存在似乎很奇怪。是时候最终将其添加了吗?

如果你想的话,可以使用以下代码:

var _addEventListener = EventTarget.prototype.addEventListener;
var _removeEventListener = EventTarget.prototype.removeEventListener;
EventTarget.prototype.events = {};
EventTarget.prototype.addEventListener = function(name, listener, etc) {
  var events = EventTarget.prototype.events;
  if (events[name] == null) {
    events[name] = [];
  }

  if (events[name].indexOf(listener) == -1) {
    events[name].push(listener);
  }

  _addEventListener(name, listener);
};
EventTarget.prototype.removeEventListener = function(name, listener) {
  var events = EventTarget.prototype.events;

  if (events[name] != null && events[name].indexOf(listener) != -1) {
    events[name].splice(events[name].indexOf(listener), 1);
  }

  _removeEventListener(name, listener);
};
EventTarget.prototype.hasEventListener = function(name) {
  var events = EventTarget.prototype.events;
  if (events[name] == null) {
    return false;
  }

  return events[name].length;
};

5

如果您想检查是否存在EventListener,可以使用Chrome检查器手动查看。在元素选项卡中,您会看到传统的“样式”子选项卡,以及紧挨着它的另一个选项卡:“事件侦听器”。其中将给出所有EventListeners及其链接元素的列表。


4
似乎没有跨浏览器的函数可以搜索给定元素下注册的事件。但是在某些浏览器中,可以使用开发工具查看元素的回调函数。这对于确定网页功能或调试代码非常有用。 Firefox 首先,在开发工具的检查器选项卡中查看元素。可以通过以下方式完成:
  • 在页面上右键单击要检查的项目,并从菜单中选择“检查元素”。
  • 在控制台中使用选择元素的函数(如document.querySelector),然后单击旁边的图标将其在“检查器”选项卡中查看。
如果向元素注册了任何事件,则会看到一个包含单词“Event”的按钮。单击它将允许您查看已向元素注册的事件。单击事件旁边的箭头可查看其回调函数。 Chrome 首先,在开发工具的“Elements”选项卡中查看元素。可以通过以下方式完成:
  • 在页面上右键单击要检查的项,然后从菜单中选择“检查”
  • 在控制台中使用选择元素的函数(例如document.querySelector),右键单击元素,然后选择“在元素面板中显示”以在“检查器”选项卡中查看它。
在窗口显示包含网页元素树的部分附近,应该有另一个带有标题“事件侦听器”的部分。选择它以查看向元素注册的事件。要查看给定事件的代码,请单击其右侧的链接。 在Chrome中,还可以使用getEventListeners函数找到元素的事件。但是,根据我的测试,当多个元素传递给它时,getEventListeners函数不列出事件。如果要查找页面上具有侦听器并查看这些侦听器的回调函数的所有元素,则可以使用控制台中的以下代码来完成:
var elems = document.querySelectorAll('*');

for (var i=0; i <= elems.length; i++) {
    var listeners = getEventListeners(elems[i]);

    if (Object.keys(listeners).length < 1) {
        continue;
    }

    console.log(elems[i]);

    for (var j in listeners) {
        console.log('Event: '+j);

        for (var k=0; k < listeners[j].length; k++) {
            console.log(listeners[j][k].listener);
        }
    }
}
如果您知道在给定的浏览器或其他浏览器中实现此功能的方法,请编辑此答案。

4

2022年更新:

我在TypeScript中编写了一些实用方法,基于这个答案来附加和分离事件,但是修改过的版本。希望有人会发现它有用。

export const attachEvent = (
  element: Element,
  eventName: string,
  callback: () => void
) => {
  if (element && eventName && element.getAttribute('listener') !== 'true') {
    element.setAttribute('listener', 'true');
    element.addEventListener(eventName, () => {
      callback();
    });
  }
};

export const detachEvent = (
  element: Element,
  eventName: string,
  callback: () => void
) => {
  if (eventName && element) {
    element.removeEventListener(eventName, callback);
  }
};

导入并且可以像这样在任何地方使用

  attachEvent(domElement, 'click', this.myfunction.bind(this));
  detachEvent(domElement, 'click', this.myfunction);

2
这可以防止添加不同类型的监听器,例如mouseenter/leave,因此您可能需要设置“listener”+事件名称属性,而不仅仅是“listener”。 - Evgeny

3

// 只需将此代码放在匿名函数外部并分配属性即可

const element = document.getElementById('div');
if (element && !element.hasAttribute('listenerOnClick')) {
    element.addEventListener('click', function () {
        const elementClicked = this;
        // fnDoAnything(this); // maybe call a function with the elementClicked...
        console.log('event has been attached');
    });
}
element.setAttribute('listenerOnClick', 'true');

5
唯一建议-element.setAttribute('listenerOnClick', 'true');应该放在if语句内。 - Dazed
我编写了一个实用方法 https://dev59.com/z2gu5IYBdhLWcg3wRk9h#71247657 - minigeek

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