调用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>
问题原因与修复方案
核心问题
- 事件绑定方式冲突:原代码混用
onclick赋值与addEventListener绑定事件,removeEventListener无法移除onclick设置的函数,导致事件叠加。 - 函数作用域错误:在
buyWeapon函数定义前调用removeEventListener,此时函数未初始化,移除操作无效。 - 未处理全部按钮:仅给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);
修复说明
- 统一事件绑定方式:全部使用
addEventListener,通过保存事件处理函数引用,实现精准移除旧事件。 - 调整函数定义顺序:先定义
buyWeapon函数,再执行事件绑定/移除操作,确保函数存在。 - 处理全部按钮事件:购买阶段给四个按钮都绑定事件,返回商店时统一清除,避免残留逻辑。
内容的提问来源于stack exchange,提问作者TheComic2222
相关产品推荐
相关产品推荐

