You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

调用removeEventListener后事件监听器未移除,RPG游戏按钮功能异常

问题描述

我正在制作一款受freeCodeCamp启发的RPG游戏,加入了自定义的“购买武器”模块,玩家可选择想要购买的武器。正常运行到返回store函数时,尽管已添加removeEventListener,按钮功能却突然未发生改变。

原代码

JavaScript代码

let xp = 0;
let health = 100;
let coin = 1000;
let inv = [{
  weapon: "Spiderweb",
  damage: 2
}];

const xpNum = document.querySelector("#xpNum");
const healthNum = document.querySelector("#healthNum");
const coinNum = document.querySelector("#goldNum");
const button1 = document.querySelector("#button1");
const button2 = document.querySelector("#button2");
const button3 = document.querySelector("#button3");
const button4 = document.querySelector("#button4");
const textbox = document.querySelector("#textbox");
const textbox2 = document.querySelector("#textbox2");

xpNum.innerHTML = xp;
healthNum.innerHTML = health;
coinNum.innerHTML = coin;

const weapons = [{
    weapon: "Shield",
    damage: 5,
    price: 50
  },
  {
    weapon: "Taser",
    damage: 10,
    price: 75
  },
  {
    weapon: "Gun",
    damage: 15,
    price: 100
  },
  {
    weapon: "Trident",
    damage: 20,
    price: 200
  },
];

const villains = [{
    villain: "Sandman",
    health: 40,
    place: "under"
  },
  {
    villain: "Robber",
    health: 20,
    place: "city"
  },
  {
    villain: "Kidnapper",
    health: 30,
    place: "city"
  },
  {
    villain: "Green Goblin",
    health: 50,
    place: "city"
  },
  {
    villain: "Mysterio",
    health: 50,
    place: "under"
  },
  {
    villain: "Lizard",
    health: 50,
    place: "under"
  },
  {
    villain: "Kingpin",
    health: 100,
    place: "city"
  },
];

const places = [{
  place: "store",
  text: "Spiderman is inside the store. What should he do?",
  btnText: ["Buy Weapons", "Sell Weapons", "Increase Health", "Leave Store"],
  btnFunc: [buy, sell, increase, start],
  text2: "Im boutta cum",
}, ];

function update(location) {
  button1.innerHTML = location.btnText[0];
  button2.innerHTML = location.btnText[1];
  button3.innerHTML = location.btnText[2];
  button4.innerHTML = location.btnText[3];

  button1.onclick = location.btnFunc[0];
  button2.onclick = location.btnFunc[1];
  button3.onclick = location.btnFunc[2];
  button4.onclick = location.btnFunc[3];

  textbox.innerHTML = location.text;

  if (Object.hasOwn(location, "text2")) {
    textbox2.innerHTML = location.text2;
  } else {
    textbox2.style.display = "none";
  }
}

function store() {
  update(places[0]);
}

function buy() {
  let weaponsText = "";
  inv.forEach((item, index) => {
    weaponsText += `${index + 1}. ${item.weapon}<br>`;
  });
  textbox2.innerHTML =
    "1. Shield - 5 Damage - 50 Coins<br>2. Taser - 10 Damage - 75 Coins<br>3. Gun - 15 Damage - 100 Coins<br>4. Trident - 20 Damage - 200 Coins<br>";
  textbox.innerHTML = `Spiderman currently has <br>${weaponsText} Which weapon should Spiderman buy?`;
  button1.innerHTML = "Shield";
  button2.innerHTML = "Taser";
  button3.innerHTML = "Gun";
  button4.innerHTML = "Trident";

  button1.removeEventListener("click", buyWeapon);

  const buyWeapon = (cmd) => {
    let weaponName = cmd.target.innerHTML;
    let weapon = weapons.find((item) => item.weapon == weaponName);
    if (weapon) {
      inv.push(weapon);
      alert(`${weaponName} bought!`);
      coin -= weapon.price;
      coinNum.innerHTML = coin;
      cmd.target.removeEventListener("click", buyWeapon);
      store();
    }
  };

  button1.addEventListener("click", buyWeapon);
}

function sell() {}

function increase() {}

function start() {}

button1.addEventListener("click", () => {
  store();
});

HTML代码

<!doctype html>
<html>

<head>
  <meta charset="utf-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <title>RPG Game</title>
  <meta name="description" content="" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <link rel="stylesheet" href="styles.css" />
</head>

<body>
  <div id="container">
    <div id="stats">
      <span id="xpText" class="stat">XP : <strong id="xpNum">0</strong></span> |
      <span id="healthText" class="stat">Health : <strong id="healthNum">0</strong></span> |
      <span id="coinsText" class="stat">Coins : <strong id="goldNum">0</strong></span>
    </div>
    <div id="buttons">
      <button id="button1" class="button">Go to store</button>
      <button id="button2" class="button">Go to city</button>
      <button id="button3" class="button">Go to underground</button>
      <button id="button4" class="button">Exit</button>
    </div>
    <div id="textbox2">ddddddd</div>
    <div id="textbox">Spiderman is near a mom-and-pop shop in Queens. What should he do?</div>
  </div>

  <script src="script.js" async defer></script>
</body>

</html>

问题原因与修复方案

核心问题

  1. 事件绑定方式冲突:原代码混用onclick赋值与addEventListener绑定事件,removeEventListener无法移除onclick设置的函数,导致事件叠加。
  2. 函数作用域错误:在buyWeapon函数定义前调用removeEventListener,此时函数未初始化,移除操作无效。
  3. 未处理全部按钮:仅给button1绑定购买事件,返回商店后其他按钮仍保留购买逻辑。

修复后的JavaScript代码

let xp = 0;
let health = 100;
let coin = 1000;
let inv = [{
  weapon: "Spiderweb",
  damage: 2
}];

const xpNum = document.querySelector("#xpNum");
const healthNum = document.querySelector("#healthNum");
const coinNum = document.querySelector("#goldNum");
const button1 = document.querySelector("#button1");
const button2 = document.querySelector("#button2");
const button3 = document.querySelector("#button3");
const button4 = document.querySelector("#button4");
const textbox = document.querySelector("#textbox");
const textbox2 = document.querySelector("#textbox2");

xpNum.innerHTML = xp;
healthNum.innerHTML = health;
coinNum.innerHTML = coin;

const weapons = [{
    weapon: "Shield",
    damage: 5,
    price: 50
  },
  {
    weapon: "Taser",
    damage: 10,
    price: 75
  },
  {
    weapon: "Gun",
    damage: 15,
    price: 100
  },
  {
    weapon: "Trident",
    damage: 20,
    price: 200
  },
];

const villains = [{
    villain: "Sandman",
    health: 40,
    place: "under"
  },
  {
    villain: "Robber",
    health: 20,
    place: "city"
  },
  {
    villain: "Kidnapper",
    health: 30,
    place: "city"
  },
  {
    villain: "Green Goblin",
    health: 50,
    place: "city"
  },
  {
    villain: "Mysterio",
    health: 50,
    place: "under"
  },
  {
    villain: "Lizard",
    health: 50,
    place: "under"
  },
  {
    villain: "Kingpin",
    health: 100,
    place: "city"
  },
];

const places = [{
  place: "store",
  text: "Spiderman is inside the store. What should he do?",
  btnText: ["Buy Weapons", "Sell Weapons", "Increase Health", "Leave Store"],
  btnFunc: [buy, sell, increase, start],
  text2: "Im boutta cum",
}, ];

// 存储每个按钮当前的事件处理函数引用,用于精准移除
const currentBtnHandlers = {
  button1: null,
  button2: null,
  button3: null,
  button4: null
};

function update(location) {
  // 先移除所有按钮的旧事件监听器
  Object.entries(currentBtnHandlers).forEach(([btnId, handler]) => {
    if (handler) {
      document.querySelector(`#${btnId}`).removeEventListener("click", handler);
    }
  });

  // 更新按钮文本
  button1.innerHTML = location.btnText[0];
  button2.innerHTML = location.btnText[1];
  button3.innerHTML = location.btnText[2];
  button4.innerHTML = location.btnText[3];

  // 绑定新事件并保存引用
  currentBtnHandlers.button1 = location.btnFunc[0];
  currentBtnHandlers.button2 = location.btnFunc[1];
  currentBtnHandlers.button3 = location.btnFunc[2];
  currentBtnHandlers.button4 = location.btnFunc[3];

  button1.addEventListener("click", currentBtnHandlers.button1);
  button2.addEventListener("click", currentBtnHandlers.button2);
  button3.addEventListener("click", currentBtnHandlers.button3);
  button4.addEventListener("click", currentBtnHandlers.button4);

  textbox.innerHTML = location.text;

  if (Object.hasOwn(location, "text2")) {
    textbox2.innerHTML = location.text2;
    textbox2.style.display = "block";
  } else {
    textbox2.style.display = "none";
  }
}

function store() {
  update(places[0]);
}

// 存储购买阶段的事件处理函数引用
let buyPhaseHandlers = [];

function buy() {
  let weaponsText = "";
  inv.forEach((item, index) => {
    weaponsText += `${index + 1}. ${item.weapon}<br>`;
  });
  textbox2.innerHTML =
    "1. Shield - 5 Damage - 50 Coins<br>2. Taser - 10 Damage - 75 Coins<br>3. Gun - 15 Damage - 100 Coins<br>4. Trident - 20 Damage - 200 Coins<br>";
  textbox.innerHTML = `Spiderman currently has <br>${weaponsText} Which weapon should Spiderman buy?`;
  button1.innerHTML = "Shield";
  button2.innerHTML = "Taser";
  button3.innerHTML = "Gun";
  button4.innerHTML = "Trident";

  // 移除之前的购买事件(如果存在)
  buyPhaseHandlers.forEach((handler, idx) => {
    [button1, button2, button3, button4][idx].removeEventListener("click", handler);
  });
  buyPhaseHandlers = [];

  // 定义购买处理函数
  const buyWeapon = (cmd) => {
    let weaponName = cmd.target.innerHTML;
    let weapon = weapons.find((item) => item.weapon === weaponName);
    if (weapon) {
      inv.push(weapon);
      alert(`${weaponName} bought!`);
      coin -= weapon.price;
      coinNum.innerHTML = coin;
      // 返回商店前清除所有购买事件
      buyPhaseHandlers.forEach((handler, idx) => {
        [button1, button2, button3, button4][idx].removeEventListener("click", handler);
      });
      buyPhaseHandlers = [];
      store();
    }
  };

  // 给所有按钮绑定购买事件
  button1.addEventListener("click", buyWeapon);
  button2.addEventListener("click", buyWeapon);
  button3.addEventListener("click", buyWeapon);
  button4.addEventListener("click", buyWeapon);
  buyPhaseHandlers = [buyWeapon, buyWeapon, buyWeapon, buyWeapon];
}

function sell() {}

function increase() {}

function start() {}

// 初始化第一个按钮事件
currentBtnHandlers.button1 = () => store();
button1.addEventListener("click", currentBtnHandlers.button1);

修复说明

  1. 统一事件绑定方式:全部使用addEventListener,通过保存事件处理函数引用,实现精准移除旧事件。
  2. 调整函数定义顺序:先定义buyWeapon函数,再执行事件绑定/移除操作,确保函数存在。
  3. 处理全部按钮事件:购买阶段给四个按钮都绑定事件,返回商店时统一清除,避免残留逻辑。

内容的提问来源于stack exchange,提问作者TheComic2222

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 18:52:32