JS答题器函数触发次数倍增问题排查求助
问题:答题系统点击答案后函数触发次数翻倍
我开发了一个基于对象的答题系统:首页可选择难度等级,JS从数组中读取所有题目并打乱选项顺序;点击答案后,JS移除数组首项并展示下一题。前两次操作正常,但从第三次开始,函数会连续触发2次,后续依次变为4次、8次等,无法定位问题。
问题原因
核心问题是重复绑定点击事件:每次调用test()函数时,都会执行pickAnswer(),而pickAnswer()会给所有.answer元素重新绑定一次点击事件。第一次调用test()绑定4个事件,第二次又绑定4个,点击时所有绑定的事件都会触发,导致触发次数每次翻倍(2、4、8...)。
另外代码里还有两处冗余:
currentQuestID的获取没必要用find,直接取randomQuestionsIDs[0]即可,因为find返回的就是数组第一个匹配的元素,完全多余。randomAnswers用forEach不会返回新数组,这里直接修改了原数组的answers顺序,虽然功能正常,但写法可以优化。
修复方案
- 将点击事件绑定移到初始化阶段,只绑定一次,避免重复绑定。
- 移除冗余的
currentQuestID查找逻辑。 - 优化答案打乱的逻辑,避免直接修改原数据(可选)。
修改后的完整代码
HTML(无改动)
<!DOCTYPE html> <html lang="pl"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <link rel="stylesheet" href="/css/main.min.css" /> </head> <body class="gothicQuiz" style="background-color: rgb(76,55,55); position: relative"> <main> <div class="choose-difficulty"> <span>Wybierz poziom trudności:</span> <div class="easy">Easy</div> </div> </main> <div class="qAndA nvisible"> <div class="question"> <h2>Pytanie <span class="questionNumber"></span>:</h2> <h3 class="questionValue"></h3> </div> <div class="answers"> <div class="answer answer1"><button class="answerBtn"></button></div> <div class="answer answer2"><button class="answerBtn"></button></div> <div class="answer answer3"><button class="answerBtn"></button></div> <div class="answer answer4"><button class="answerBtn"></button></div> </div> </div> <div class="ending"></div> </body> </html>
CSS(无改动)
body { color: white }
JavaScript(关键修改标注)
const gothicTheme = { themeName: "Gothic", questionsEasy: [ { id: 1, question: "dsaasd", answers: [ { text: "sda", correct: true }, { text: "Disdago", correct: false }, { text: "Y'asdedsarion", correct: false }, { text: "Wsa", correct: false }, ], }, { id: 2, question: "Która osoba z podanych NIE jest przyjacielem Bezimiennego?", answers: [ { text: "Cipher", correct: true }, { text: "Lee", correct: false }, { text: "Milten", correct: false }, { text: "Diego", correct: false }, ], }, { id: 3, question: "Kto jest dowódcą paladynów w Górniczej Dolinie, a kto w Khorinis?", answers: [ { text: "Khorinis - Lord Hagen, GD - Garond", correct: true }, { text: "Khorinis - Lord Garond, GD - Hagen", correct: false }, { text: "Khorinis - Lord Rhobar, GD - Orik", correct: false }, { text: "Khorinis - Lord Orik, GD - Rhobar", correct: false }, ], }, { id: 4, question: "Które z podanych krain możemy odwiedzić podczas gameplayu Gothica 3", answers: [ { text: "Myrtana, Nordmar, Varant", correct: true }, { text: "Myrtana, Północne Ziemie, Varant", correct: false }, { text: "Normard, Varant, Północne Ziemie", correct: false }, { text: "Wyspy Południowe, Północne Ziemie, Niezbadane Krainy", correct: false }, ], }, { id: 5, question: "Do oficjalnego kanonu zaliczamy, które części Gothica?", answers: [ { text: "Gothic, Gothic 2, Gothic 2: Noc Kruka, Gothic 3", correct: true }, { text: "Gothic, Gothic 2: Noc Kruka, Gothic 3", correct: false }, { text: "Gothic, Gothic 2, Gothic 3", correct: false }, { text: "Gothic, Gothic: Mroczne Tajemnice, Gothic 2, Gothic 2: Noc Kruka, Gothic 3, Gothic 3: Zmierzch Bogów", correct: false }, ], }, { id: 6, question: "Kto jest głównym antagonistą w dwóch pierwszych częściach gry", answers: [ { text: "Śniący i Smok Ożywieniec", correct: true }, { text: "Śniący i Bestia ", correct: false }, { text: "Rhobar", correct: false }, { text: "Wrzód", correct: false }, ], }, { id: 7, question: "Kto spuszcza łomot Bezimiennemu po zesłaniu do kolonii?", answers: [ { text: "Bullit", correct: true }, { text: "Diego", correct: false }, { text: "Rhobar", correct: false }, { text: "Wrzód", correct: false }, ], }, { id: 8, question: "Kto spuszcza łomot Bezimiennemu po zesłaniu do kolonii?", answers: [ { text: "Bullit", correct: true }, { text: "Diego", correct: false }, { text: "Rhobar", correct: false }, { text: "Wrzód", correct: false }, ], }, { id: 9, question: "Kto spuszcza łomot Bezimiennemu po zesłaniu do kolonii?", answers: [ { text: "Bullit", correct: true }, { text: "Diego", correct: false }, { text: "Rhobar", correct: false }, { text: "Wrzód", correct: false }, ], }, { id: 10, question: "Kto spuszcza łomot Bezimiennemu po zesłaniu do kolonii?", answers: [ { text: "Bullit", correct: true }, { text: "Diego", correct: false }, { text: "Rhobar", correct: false }, { text: "Wrzód", correct: false }, ], }, ], }; const difficulties = document.querySelectorAll(".easy, .medium, .hard"); const answers = document.querySelectorAll(".answer"); let questNumber = 1; let randomQuestionsIDs = []; // 初始化题目ID数组并打乱 function initQuestions() { const questionsIDs = gothicTheme.questionsEasy.map(e => e.id); randomQuestionsIDs = questionsIDs.sort(() => Math.random() - 0.5); } // 打乱单个题目的答案顺序(不修改原数据) function shuffleAnswers(answers) { return [...answers].sort(() => Math.random() - 0.5); } const test = () => { if (randomQuestionsIDs.length === 0) { // 题目全部做完的逻辑,比如显示结束界面 document.querySelector(".qAndA").classList.add("nvisible"); document.querySelector(".ending").textContent = "Quiz zakończony!"; return; } // 直接取第一个ID,去掉冗余的find const currentQuestID = randomQuestionsIDs[0]; const currentQuest = gothicTheme.questionsEasy.find(e => e.id === currentQuestID); // 打乱当前题目的答案,不修改原数据 const shuffledAnswers = shuffleAnswers(currentQuest.answers); document.querySelector("h3.questionValue").textContent = currentQuest.question; document.querySelector(".questionNumber").textContent = questNumber; // 渲染答案 answers.forEach((el, index) => { el.textContent = shuffledAnswers[index].text; // 可以把正确答案标识存在元素上,方便后续判断对错 el.dataset.correct = shuffledAnswers[index].correct; }); questNumber++; }; // 只绑定一次点击事件,避免重复绑定 answers.forEach(ele => { ele.addEventListener("click", () => { randomQuestionsIDs.shift(); test(); }); }); // 难度选择事件 document.querySelector(".easy").addEventListener("click", () => { document.querySelector("main").classList.toggle("nvisible"); document.querySelector(".qAndA").classList.toggle("nvisible"); initQuestions(); questNumber = 1; test(); });
关键改动说明
- 将点击事件绑定移到初始化阶段:只给
.answer元素绑定一次点击事件,不再每次调用test()时重复绑定,彻底解决触发次数翻倍的问题。 - 优化题目ID初始化:新增
initQuestions()函数专门处理题目ID的打乱,避免全局变量污染。 - 答案打乱逻辑优化:新增
shuffleAnswers()函数,通过扩展运算符复制原答案数组再打乱,避免修改原数据。 - 移除冗余代码:删除了没必要的
currentQuestID查找逻辑,直接取数组第一个元素。 - 增加结束判断:当题目数组为空时,显示结束提示,避免报错。
内容的提问来源于stack exchange,提问作者Nordslig
相关产品推荐
相关产品推荐

