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

JavaScript循环下作用域与事件处理实现不规则动词测试的问题

实现方案

JS 不需要强制使用类实现状态管理,用闭包就能实现和Python类self属性相同的状态共享效果,代码更轻量,完全满足你的需求。

你现有代码的核心问题有两个:

  • 每次加载题目都会给按钮新增点击事件监听,多次运行后会重复触发check逻辑
  • JS 是事件驱动的单线程运行模型,不能用Python里常用的同步循环实现连续提问,需要靠用户点击事件驱动下一题加载

改造思路

  1. 用立即执行函数(IIFE)创建闭包,把得分、剩余动词列表、当前题目数、当前动词这些状态都存在闭包内,所有内部函数可以直接共享访问,不需要额外传参,也不会污染全局作用域
  2. 事件监听器只在初始化的时候绑定一次,避免重复绑定
  3. 拆分独立的getNextVerb()函数实现随机抽题逻辑
  4. 判分完成后自动触发下一题加载,达到最大题数后展示结算结果
  5. 保留main函数作为初始化入口,同时支持重启功能

改造后完整代码

// 闭包包裹所有逻辑,内部变量仅内部可访问,等效Python类的实例作用域
(function() {
  let score = 0
  const maxQuestions = 10
  let currentVerb = null
  let leftVerbs = []
  let answeredCount = 0

  function loadJSON(url, success, error) {
    const xhr = new XMLHttpRequest()
    xhr.open("GET", url, true)
    xhr.responseType = 'json'
    xhr.send()
    xhr.onload = function() {
        if (xhr.status == 200) {
            success(xhr.response)
        } else {
            error(xhr)
        }
    }
  }

  // 独立抽题函数
  function getNextVerb() {
    const randomIndex = Math.floor(Math.random() * leftVerbs.length)
    // 取出并删除当前抽到的动词,避免重复出题
    return leftVerbs.splice(randomIndex, 1)[0]
  }

  // 加载下一题
  function renderNextQuestion() {
    // 清空输入框
    document.getElementById('preterit').value = ''
    document.getElementById('participle').value = ''
    // 抽题
    currentVerb = getNextVerb()
    // 展示当前动词原型
    document.getElementById('present').innerHTML = currentVerb[0]
  }

  function check() {
    const preterit = currentVerb[1].split('/')
    const participle = currentVerb[2].split('/')
    const userPreterit = document.getElementById('preterit').value.trim()
    const userParticiple = document.getElementById('participle').value.trim()

    if (preterit.includes(userPreterit)) score += 1
    if (participle.includes(userParticiple)) score += 1

    document.getElementById('score').innerHTML = score
    answeredCount += 1

    // 判断是否达到最大题数
    if (answeredCount < maxQuestions) {
      renderNextQuestion()
    } else {
      // 答题结束展示结算,可自定义提示
      document.getElementById('present').innerHTML = `答题结束!最终得分:${score}/${maxQuestions * 2}`
      // 隐藏按钮或者禁用,也可以加重启按钮调用main方法重启
      document.getElementById('button').disabled = true
    }
  }

  function error() {
    console.log('Error loading JSON file.')
  }

  // main作为全局唯一入口,负责初始化、绑定事件、重启功能
  window.main = function(verbs) {
    // 重置所有状态
    score = 0
    answeredCount = 0
    leftVerbs = [...verbs] // 复制原始动词列表,避免修改原数据
    document.getElementById('score').innerHTML = score
    document.getElementById('button').disabled = false
    // 先移除旧监听再绑定新监听,避免多次调用main导致重复绑定
    document.getElementById('button').removeEventListener('click', check)
    document.getElementById('button').addEventListener('click', check)
    // 加载第一题
    renderNextQuestion()
  }

  loadJSON('js/verbs-list.json', main, error)
})()

可选类实现方案

如果你更习惯Python面向对象的写法,JS也可以用类实现,逻辑和Python几乎一致:

class VerbTest {
  constructor(verbs) {
    this.score = 0
    this.maxQuestions = 10
    this.leftVerbs = [...verbs]
    this.answeredCount = 0
    this.currentVerb = null
    this.initEventListener()
    this.renderNextQuestion()
  }

  initEventListener() {
    document.getElementById('button').addEventListener('click', () => this.check())
  }

  getNextVerb() {
    const randomIndex = Math.floor(Math.random() * this.leftVerbs.length)
    return this.leftVerbs.splice(randomIndex, 1)[0]
  }

  renderNextQuestion() {
    document.getElementById('preterit').value = ''
    document.getElementById('participle').value = ''
    this.currentVerb = this.getNextVerb()
    document.getElementById('present').innerHTML = this.currentVerb[0]
  }

  check() {
    const preterit = this.currentVerb[1].split('/')
    const participle = this.currentVerb[2].split('/')
    const userPreterit = document.getElementById('preterit').value.trim()
    const userParticiple = document.getElementById('participle').value.trim()

    if (preterit.includes(userPreterit)) this.score += 1
    if (participle.includes(userParticiple)) this.score += 1

    document.getElementById('score').innerHTML = this.score
    this.answeredCount += 1

    if (this.answeredCount < this.maxQuestions) {
      this.renderNextQuestion()
    } else {
      document.getElementById('present').innerHTML = `答题结束!最终得分:${this.score}/${this.maxQuestions * 2}`
      document.getElementById('button').disabled = true
    }
  }
}

// 调用逻辑
function loadJSON(url, success, error) {
  const xhr = new XMLHttpRequest()
  xhr.open("GET", url, true)
  xhr.responseType = 'json'
  xhr.send()
  xhr.onload = function() {
      if (xhr.status == 200) {
          success(xhr.response)
      } else {
          error(xhr)
      }
  }
}

loadJSON('js/verbs-list.json', (verbs) => new VerbTest(verbs), () => console.log('加载失败'))

两种方案都可以实现你的需求,小型应用用闭包更简洁,后续如果要加更多功能用类扩展性更好。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.05 12:57:03