为什么使用 `removeEventListener` 的这段代码似乎无法正确解除绑定按键事件?

Why does this code using `removeEventListener` not seem to unbind key events correctly?

我希望我的代码做什么

当我单击第一个按钮然后按下 Space 键时,它应该会提示 "hello world"。当我单击第二个按钮然后按下 Space 键时,我希望它会提醒 "hello"。对第三个按钮重复该操作,它应该提醒 "hello3".

代码的当前结果是什么

当我点击第一个按钮时,它起作用了:它提醒 "hello world"。但问题是当我点击第二个或第三个按钮时:它仍然提醒 "hello world".

我的代码有什么问题?

function usekeyUp() {
  document.addEventListener("keyup", keyUp);
}

function keyUp(event) {
  document.removeEventListener("keyup", keyUp2);
  document.removeEventListener("keyup", keyUp3);
  
  if (event.code === 'Space') {
    alert("hello world")
  }
}

function usekeyUp2() {
  document.addEventListener("keyup", keyUp2);
}

function keyUp2(event) {
  document.removeEventListener("keyup", keyUp);
  document.removeEventListener("keyup", keyUp3);
  
  if (event.code === 'Space') {
    alert("hello")
  }
}

function usekeyUp3() {
  document.addEventListener("keyup", keyUp3);
}

function keyUp3(event) {
  document.removeEventListener("keyup", keyUp);
  document.removeEventListener("keyup", keyUp2);
  
  if (event.code === 'Space') {
    alert("hello3")
  }
}
<button onclick="usekeyUp()">click</button>
<button onclick="usekeyUp2()">click</button>
<button onclick="usekeyUp3()">click</button>

目前,当您点击Space时,所有事件侦听器仍然绑定(从按钮点击开始),并且每个回调按顺序执行。如果您按下第一个按钮,usekeyUp 将绑定 keyUp。点击 Space 执行 keyUp,解除绑定 keyUp2keyUp3 并提醒 "hello world"。它解除绑定keyUp

如果您随后单击其他按钮,例如第二个,它另外绑定keyUp2。但是如果你点击Space,那么当前绑定的事件监听器依次是keyUpkeyUp2。首先,keyUp执行,解除绑定keyUp2keyUp3。现在,没有其他事件侦听器被绑定,但是 keyUp 永远保持绑定

documentation:

中有关于此行为的说明

If an EventListener is removed from an EventTarget while it is processing an event, it will not be triggered by the current actions. An EventListener will not be invoked for the event it was registered for after being removed. However, it can be reattached.

所以,removeEventListener的“工作”,但从未达到正确的。

如果您想继续使用 removeEventListener,请将 removeEventListener 行移动到相应的 click 事件中,而不是 keyup 事件中。

onclick 这样的内联事件处理程序是 not recommended. They are an obsolete, hard-to-maintain and unintuitive way of registering events. Always use addEventListener

document.getElementById("click1").addEventListener("click", function(){
  document.addEventListener("keyup", keyUp1);
  document.removeEventListener("keyup", keyUp2);
  document.removeEventListener("keyup", keyUp3);
});
document.getElementById("click2").addEventListener("click", function(){
  document.removeEventListener("keyup", keyUp1);
  document.addEventListener("keyup", keyUp2);
  document.removeEventListener("keyup", keyUp3);
});
document.getElementById("click3").addEventListener("click", function(){
  document.removeEventListener("keyup", keyUp1);
  document.removeEventListener("keyup", keyUp2);
  document.addEventListener("keyup", keyUp3);
});

function keyUp1(event){
  if (event.code === "Space") {
    alert("hello world")
  }
}

function keyUp2(event){
  if (event.code === "Space") {
    alert("hello")
  }
}

function keyUp3(event){
  if (event.code === "Space") {
    alert("hello3")
  }
}
<input id="click1" type="button" value="First"/>
<input id="click2" type="button" value="Second"/>
<input id="click3" type="button" value="Third"/>

因为这段代码相当WET, a better approach would use event delegation instead of assigning multiple events — it’s more maintainable, and applies to dynamically added elements. E.g., use an event argument’s target. See the tag info and What is DOM Event delegation?

{
  let currentAlert;

  addEventListener("keyup", ({code}) => {
    if (currentAlert && code === "Space") {
      alert(currentAlert);
    }
  });

  addEventListener("click", ({target}) => {
    if(target?.dataset?.alert){
      currentAlert = target.dataset.alert;
    }
  });
}
<input data-alert="hello world" type="button" value="First" />
<input data-alert="hello" type="button" value="Second" />
<input data-alert="hello3" type="button" value="Third" />