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

JS答题器函数触发次数倍增问题排查求助

问题:答题系统点击答案后函数触发次数翻倍

我开发了一个基于对象的答题系统:首页可选择难度等级,JS从数组中读取所有题目并打乱选项顺序;点击答案后,JS移除数组首项并展示下一题。前两次操作正常,但从第三次开始,函数会连续触发2次,后续依次变为4次、8次等,无法定位问题。

问题原因

核心问题是重复绑定点击事件:每次调用test()函数时,都会执行pickAnswer(),而pickAnswer()会给所有.answer元素重新绑定一次点击事件。第一次调用test()绑定4个事件,第二次又绑定4个,点击时所有绑定的事件都会触发,导致触发次数每次翻倍(2、4、8...)。

另外代码里还有两处冗余:

  • currentQuestID的获取没必要用find,直接取randomQuestionsIDs[0]即可,因为find返回的就是数组第一个匹配的元素,完全多余。
  • randomAnswers用forEach不会返回新数组,这里直接修改了原数组的answers顺序,虽然功能正常,但写法可以优化。

修复方案

  1. 将点击事件绑定移到初始化阶段,只绑定一次,避免重复绑定。
  2. 移除冗余的currentQuestID查找逻辑。
  3. 优化答案打乱的逻辑,避免直接修改原数据(可选)。

修改后的完整代码

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();
});

关键改动说明

  1. 将点击事件绑定移到初始化阶段:只给.answer元素绑定一次点击事件,不再每次调用test()时重复绑定,彻底解决触发次数翻倍的问题。
  2. 优化题目ID初始化:新增initQuestions()函数专门处理题目ID的打乱,避免全局变量污染。
  3. 答案打乱逻辑优化:新增shuffleAnswers()函数,通过扩展运算符复制原答案数组再打乱,避免修改原数据。
  4. 移除冗余代码:删除了没必要的currentQuestID查找逻辑,直接取数组第一个元素。
  5. 增加结束判断:当题目数组为空时,显示结束提示,避免报错。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 05:48:05