Javascript "removeEventListener" 没有对匿名函数做任何事情,没有简单的方法吗?

Javascript "removeEventListener" not doing anything with anonymous function, no easy way to do this?

我在网站上看到很多关于同一主题的问题(“removeEventListener 不工作”),但我阅读了我能找到的所有内容,none 其中解决了我的问题。我确实找到了一些这提出了一些变通办法(比如在范围内创建一个对象,负责添加和删除侦听器)但是 none 确实对我有用,或者我不知道如何将它们具体实现到我的代码中。这就是为什么我允许自己 post 这个。如果有人找到 post 专门解决我的问题,我会删除这个。


简而言之,我有一个 HTML table,所有“td”标签上的“contenteditable”设置为 true。我下面有一个调用 deleteMode() 函数的按钮:

        function deleteMode(){
            let tds = document.getElementsByTagName('td');
            if (delete_mode == true){
                table.style.border = null;
                for(let i = 0; i < tds.length; i++){
                    tds[i].style.cursor = null;
                    tds[i].setAttribute("contenteditable", "true");

                    tds[i].removeEventListener("mouseover", function(){ setDeleteBGC(i); });
                    tds[i].removeEventListener("mouseout", function(){ removeDeleteBGC(i); });
                    tds[i].removeEventListener("click", function(){ deleteEvent(i); });

                }
                delete_mode = false;
            }
            else{
                table.style.border = "solid 5px red";
                                
                for(let i = 0; i < tds.length; i++){
                    tds[i].style.cursor = "pointer";
                    tds[i].removeAttribute("contenteditable");

                    tds[i].addEventListener("mouseover", function(){ setDeleteBGC(i); });
                    tds[i].addEventListener("mouseout", function(){ removeDeleteBGC(i); });
                    tds[i].addEventListener("click", function(){ deleteEvent(i); });

                }
                delete_mode = true;
            }
        }

(delete_mode 变量在页面加载时设置为“false”) 对于我的 eventListeners,因为我必须向函数发送参数,所以我使用匿名函数来调用带有参数的指定函数。如果我没理解错的话,这就是问题所在。

对于那些需要查看完整代码(HTML + Javascript 正在调用的函数)的人,这里有一个 JS fiddle:

https://jsfiddle.net/67p5kLze/6/

正如您在 fiddle 中看到的那样,当用户“进入”“删除模式”时,他可以点击 4 个 4 个地清空单元格。但是,当他“退出”它时,他仍然可以通过单击清空它们,并且仍然有橙色的“预览”,我认为这是因为 EventHandlers 没有被删除。

非常感谢任何帮助。提前致谢!


根据@Yousaf 的回答,我将代码修改为如下内容:

        function deleteMode(){
            let tds = document.getElementsByTagName('td');
            if (delete_mode == true){
                table.style.border = null;
                for(let i = 0; i < tds.length; i++){
                    tds[i].style.cursor = null;
                    tds[i].setAttribute("contenteditable", "true");

                    bindFunc = deleteEvent.bind(null, i);

                    tds[i].removeEventListener("mouseover", function(){ setDeleteBGC(i); });
                    tds[i].removeEventListener("mouseout", function(){ removeDeleteBGC(i); });
                    tds[i].removeEventListener("click", bindFunc);

                }
                delete_mode = false;
            }
            else{
                table.style.border = "solid 5px red";
                                
                for(let i = 0; i < tds.length; i++){
                    tds[i].style.cursor = "pointer";
                    tds[i].removeAttribute("contenteditable");



                    var bindFunc = deleteEvent.bind(null, i);

                    tds[i].addEventListener("mouseover", function(){ setDeleteBGC(i); });
                    tds[i].addEventListener("mouseout", function(){ removeDeleteBGC(i); });
                    tds[i].addEventListener("click", bindFunc);

                }
                delete_mode = true;
            }
            function deleteEvent(index){
                let tds = document.getElementsByTagName('td');
                console.log(index);
                index = Math.floor(index/4) * 4;
                let selectInCell = tds[index].getElementsByTagName('select');
                selectInCell[0].selectedIndex = 0;
                tds[index+1].innerHTML = '';
                tds[index+2].innerHTML = '';
                tds[index+3].innerHTML = '';
            }
        }

但是,它不起作用,我想是因为 addEventListener 和 removeEventListener 不在循环中。这是我的代码与他的代码唯一不同的地方。

如果有人看到我正在做的错误,我将不胜感激!

.removeEventListener() 采用与用于添加事件侦听器的第二个参数相同的回调函数,即作为第二个参数传递给 .addEventListener() 方法。

在您的例子中,您使用的是匿名函数,因此您传递给 .addEventListener() 方法的函数不同于您传递给 .removeEventListener() 方法的回调函数。因此,不会删除已注册的事件侦听器。

由于您在问题中提到您使用匿名函数是因为您需要将参数传递给事件处理函数。您可以创建一个要用作事件处理程序的函数,然后在某处使用 Function.prototype.bind() to get another function, save a reference to this function returned by Function.prototype.bind() 然后将此新函数用作事件处理程序。

稍后,当您需要删除事件侦听器时,您可以轻松删除它们,因为事件处理函数的引用已经保存。

以下代码片段显示了一个示例:

const btn = document.querySelector('#one');
const removeBtn = document.querySelector('#two');

function handleClick(val) {
  console.log(val);
}

// 123 is the argument that we want to pass to the event handler
const bindFunc = handleClick.bind(null, 123);

btn.addEventListener('click', bindFunc);

removeBtn.addEventListener('click', () => {
  btn.removeEventListener('click', bindFunc)
});
<button id="one">Click</button>
<button id="two">Remove Click Listener</button>

编辑

这是您的代码演示,其中包含我的回答中建议的更改。

P.S: 我建议您使用此代码段下面的第二个代码段。这是因为您的代码包含 a-lot 代码重复并且不必要地复杂。我添加此代码片段只是为了向您展示如何使用我的回答中提到的方法删除事件侦听器。

var delete_mode = false;
var table = document.getElementById('main-table');
var setBgcEventListeners = [];
var removeBgcEventListeners = [];
var deleteEventListeners = [];

function deleteMode() {
  let tds = document.getElementsByTagName('td');

  if (delete_mode == true) {
    table.style.border = null;

    for (let i = 0; i < tds.length; i++) {
      tds[i].style.cursor = null;
      tds[i].setAttribute('contenteditable', 'true');

      tds[i].removeEventListener('mouseover', setBgcEventListeners[i]);
      tds[i].removeEventListener('mouseout', removeBgcEventListeners[i]);
      tds[i].removeEventListener('click', deleteEventListeners[i]);
    }
    delete_mode = false;
  }
  else {
    table.style.border = 'solid 2px red';

    for (let i = 0; i < tds.length; i++) {
      tds[i].style.cursor = 'pointer';
      tds[i].removeAttribute('contenteditable');
      
      let setDelBGC = setDeleteBGC.bind(null, i, tds);
      let removeDelBGC = removeDeleteBGC.bind(null, i, tds);
      let removeTdContent = deleteEvent.bind(null, i, tds);

      setBgcEventListeners.push(setDelBGC);
      removeBgcEventListeners.push(removeDelBGC);
      deleteEventListeners.push(removeTdContent)

      tds[i].addEventListener('mouseover', setDelBGC);
      tds[i].addEventListener('mouseout', removeDelBGC);
      tds[i].addEventListener('click', removeTdContent);
    }
    delete_mode = true;
  }
}

function setDeleteBGC(index, tds) {
  index = Math.floor(index / 4) * 4;
  tds[index].style.backgroundColor = 'orange';
  tds[index + 1].style.backgroundColor = 'orange';
  tds[index + 2].style.backgroundColor = 'orange';
  tds[index + 3].style.backgroundColor = 'orange';
}

function removeDeleteBGC(index, tds) {
  index = Math.floor(index / 4) * 4;
  tds[index].style.backgroundColor = null;
  tds[index + 1].style.backgroundColor = null;
  tds[index + 2].style.backgroundColor = null;
  tds[index + 3].style.backgroundColor = null;
}

function deleteEvent(index, tds) {
  index = Math.floor(index / 4) * 4;
  tds[index].innerHTML = '';
  tds[index + 1].innerHTML = '';
  tds[index + 2].innerHTML = '';
  tds[index + 3].innerHTML = '';
}
td,
tr,
table {
  border: 1px solid black;
  border-collapse: collapse;
}
<table id="main-table">
  <tr>
    <td contenteditable="true">Hey</td>
    <td contenteditable="true">Hey</td>
    <td contenteditable="true">Hey</td>
    <td contenteditable="true">Hey</td>
    <td contenteditable="true">Hey</td>
    <td contenteditable="true">Hey</td>
    <td contenteditable="true">Hey</td>
    <td contenteditable="true">Hey</td>
  </tr>
  <tr>
    <td contenteditable="true">Hey</td>
    <td contenteditable="true">Hey</td>
    <td contenteditable="true">Hey</td>
    <td contenteditable="true">Hey</td>
    <td contenteditable="true">Hey</td>
    <td contenteditable="true">Hey</td>
    <td contenteditable="true">Hey</td>
    <td contenteditable="true">Hey</td>
  </tr>
</table>
<button onclick="deleteMode()">Delete Mode</button>

更好的解决方案

您的代码过于复杂。您正在根据您是否处于删除模式添加和删除事件侦听器。

您可以通过在 table 元素而不是每个 td 元素。

以下代码片段显示了一个示例,说明您可以如何改进代码、使其更具可读性并消除代码重复。

var deleteMode = false;
var tds = [...document.querySelectorAll('td')];
var deleteBtn = document.getElementById('delBtn');
var table = document.getElementById('main-table');
var hoverColor = 'orange';
const groupLength = 4;

table.addEventListener('mouseover', handleEvent);
table.addEventListener('mouseout', handleEvent);
table.addEventListener('click', handleEvent);

deleteBtn.addEventListener('click', function () {
  deleteMode = !deleteMode;
  let cursor, border;

  if (deleteMode) {
    border = 'solid 2px red';
    cursor = 'pointer';
    tds.forEach(td => td.removeAttribute('contenteditable'));
  } else {
    border = cursor = null;
    tds.forEach(td => td.setAttribute('contenteditable', 'true'));
  }

  table.style.border = border;
  table.style.cursor = cursor;
});

function handleEvent(event) {
  if (!deleteMode) return;
  const target = event.target;

  if (target.matches('td')) {
    const baseIndex = getTdBaseIndex(target);
    const eventType = event.type;

    if (eventType == 'mouseover' || eventType == 'mouseout') {
      setBgColor(eventType, baseIndex);
    } else if (eventType == 'click') {
      deleteTdContent(baseIndex);
    }
  }
}

function changeBgColor(el, color = null) {
  el.style.backgroundColor = color;
}

function setBgColor(eventType, baseIndex) {
  const bgColor = eventType == 'mouseover' ? hoverColor : null;
  for (let i = baseIndex; i < baseIndex + 4; i++) {
    changeBgColor(tds[i], bgColor);
  }
}

function deleteTdContent(baseIndex) {
  for (let i = baseIndex; i < baseIndex + 4; i++) {
    tds[i].innerHTML = '';
  }
}

function getTdBaseIndex(targetTdElm) {
  let tdIndex = tds.findIndex(td => td == targetTdElm);
  return Math.floor(tdIndex / groupLength) * groupLength;
}
td,
tr,
table {
  border: 1px solid black;
  border-collapse: collapse;
}
<table id="main-table">
  <tr>
    <td contenteditable="true">Hey</td>
    <td contenteditable="true">Hey</td>
    <td contenteditable="true">Hey</td>
    <td contenteditable="true">Hey</td>
    <td contenteditable="true">Hey</td>
    <td contenteditable="true">Hey</td>
    <td contenteditable="true">Hey</td>
    <td contenteditable="true">Hey</td>
  </tr>
  <tr>
    <td contenteditable="true">Hey</td>
    <td contenteditable="true">Hey</td>
    <td contenteditable="true">Hey</td>
    <td contenteditable="true">Hey</td>
    <td contenteditable="true">Hey</td>
    <td contenteditable="true">Hey</td>
    <td contenteditable="true">Hey</td>
    <td contenteditable="true">Hey</td>
  </tr>
</table>
<button id="delBtn">Delete Mode</button>

经过深入研究,我终于想到了输入类似“删除 all eventListerners”的内容。这在普通 Javascript 中是不可能做到的,但我确实发现了 JQuery“on()”和“off()”函数。

“on()”函数基本上类似于“addEventListener()”(下面代码中的示例)

不过,“off()”函数与“removeEventListener()”函数有些不同。基本上,使用“off()”函数,您可以删除特定元素或标记的所有元素(在我的示例中为“td”标记)的所有 EventListener。这就是我想要实现的目标。

这是函数的最终形式,现在可以正常工作了:

        function deleteMode(){
            let tds = document.getElementsByTagName('td');
            if (delete_mode == true){
                table.style.border = null;
                for(let i = 0; i < tds.length; i++){
                    tds[i].style.cursor = null;
                    tds[i].setAttribute("contenteditable", "true");
                }
                $( "td" ).off();
                delete_mode = false;


            }
            else{
                table.style.border = "solid 5px red";
                
                for(let i = 0; i < tds.length; i++){
                    tds[i].style.cursor = "pointer";
                    tds[i].removeAttribute("contenteditable");
                    $(tds[i]).on("click", someNameHere);
                    $(tds[i]).on("mouseover", someNameHere2);
                    $(tds[i]).on("mouseout", someNameHere3);

                    function someNameHere(){ deleteEvent(i); }
                    function someNameHere2(){ setDeleteBGC(i); }
                    function someNameHere3(){ removeDeleteBGC(i); }

                }
                delete_mode = true;
            }
        }

我的 deleteEvent() 函数、setDeleteBCG() 函数和 removeDeleteBCG() 函数被正确触发。

请注意,这样做,您甚至不必将函数与“on()”声明分开。也就是说,我的someNameHere函数有些多余,可以用匿名函数代替。