JavaScript循环下作用域与事件处理实现不规则动词测试的问题
实现方案
JS 不需要强制使用类实现状态管理,用闭包就能实现和Python类self属性相同的状态共享效果,代码更轻量,完全满足你的需求。
你现有代码的核心问题有两个:
- 每次加载题目都会给按钮新增点击事件监听,多次运行后会重复触发
check逻辑 - JS 是事件驱动的单线程运行模型,不能用Python里常用的同步循环实现连续提问,需要靠用户点击事件驱动下一题加载
改造思路
- 用立即执行函数(IIFE)创建闭包,把得分、剩余动词列表、当前题目数、当前动词这些状态都存在闭包内,所有内部函数可以直接共享访问,不需要额外传参,也不会污染全局作用域
- 事件监听器只在初始化的时候绑定一次,避免重复绑定
- 拆分独立的
getNextVerb()函数实现随机抽题逻辑 - 判分完成后自动触发下一题加载,达到最大题数后展示结算结果
- 保留
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
相关产品推荐
相关产品推荐

