Javascript 循环中的正确范围和事件处理

Proper scope and event handling in a Javascript loop

我无法理解 Javascript 事件处理和变量范围的基本概念。我来自 Python,我在其中构建了一个小型 GUI 应用程序,它显示随机的英语不规则动词并要求用户输入过去和分词形式。我正在尝试用 JS 编写相同的东西。

score = 0
maxQuestions = 10

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

/*
The JSON file looks like this:
[
    [
        "leap",
        "leapt/leaped",
        "leapt/leaped"
    ],
etc...
*/

function main(verbs) {
    var verb = verbs.splice(Math.floor(Math.random() * verbs.length), 1)[0]
    var present = verb[0]
    document.getElementById('present').innerHTML = present
    document.getElementById('button').addEventListener('click', function() {
        check(verb)
    })
}

function check(verb) {
    var preterit = verb[1].split('/'),
        participle = verb[2].split('/')
    var user_preterit = document.getElementById('preterit').value
    var user_participle = document.getElementById('participle').value
    if (preterit.includes(user_preterit)) {
        score += 1
    }
    if (participle.includes(user_participle)) {
        score += 1
    }
    document.getElementById('score').innerHTML = score
}

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

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

这按预期工作,但我不确定如何正确构建循环以询问例如10 个问题。

我想保留 main() 函数以设置事件侦听器,有介绍性文本和重新开始的选项,但我需要将动词选择代码放入另一个函数中可以 运行 重复而不是每次都添加事件监听器。如何在保持对 verb 变量的引用的同时做到这一点?

我在 Python 中很容易做到这一点,因为该应用程序包含在 class 中。我可以使用 self 来引用“全局变量”,例如得分、当前动词和动词列表。它应该在 JS 中以相同的方式使用,即使用 class 和 this,还是可以以更简单的方式完成?

在 JS 中将 verb 称为全局变量是您使用 scoremaxQuestions 变量的方式,因为您将它们定义在任何范围之外。如果您在顶部定义 verb 并用响应填充它,您可以从任何其他函数访问它。

关于询问10次,给动态生成的元素添加事件监听器可能与静态的有点不同,你应该使用event delegation处理事件而不是每次都分配事件监听器。

作为建议,您可以使用下一个按钮显示下一个问题并将问题行的克隆元素附加到问题容器,如下所示:

let clonedQuestion = document.querySelector('#question').cloneNode(true);

// do whatever you want with the cloned element 
// like assigning the next present verb and a unique identifier

document.querySelector('#question-container').appendChild(clonedQuestion);

我稍微编辑了你的代码。在 js 中有词法作用域的概念,其中函数体外部的变量在该函数体内部可见,具体取决于它们的物理位置。 var 也被 letconst 取代。 let 当稍后可以在代码中重新分配变量时很有用。 const 常量。我这里也用了 'fetch' https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch 而不是 xhr

让我知道这是否有效:)

score = 0
maxQuestions = 10

async function loadJSON(url) {
    try {
        const res = await fetch(url)
        const verbs = await res.json()
        return verbs
    catch(err) {
       console.log(err)
    }
}

/*
The JSON file looks like this:
[
    [
        "leap",
        "leapt/leaped",
        "leapt/leaped"
    ],
etc...
*/

async function main() {
    const verbs = await loadJson('js/verbs-list.json')
    let verb = [];
    let present = ""
    // dom elements
    const presentElmt = document.getElementById('present')
    const btnElmt = document.getElementById('button')
    
    let counter = 0

    while(counter <= maxQuestions) {
        verb = getRandomVerb(verbs)
        present = verb[0]

        presentElmt.innerHTML = present

        btnElmt.addEventListener('click', function(){
            check(verb)
        })

        counter++ 

    }
}

function getRandomVerbs(verbs) {
    if(!verbs) return [] // return an empty array if list of verbs is empty
    return verbs.splice(Math.floor(Math.random() * verbs.length), 1)[0]
}

function check(verb) {
    const preterit = verb[1].split('/'),
        participle = verb[2].split('/')
    const user_preterit = document.getElementById('preterit').value
    const user_participle = document.getElementById('participle').value
    if (preterit.includes(user_preterit)) {
        score += 1
    }
    if (participle.includes(user_participle)) {
        score += 1
    }

    document.getElementById('score').innerHTML = score
}



main()

我正在回答我自己的问题,以防此解决方案可以帮助某人。在@Bergi 发表关于不可能使用循环的评论之后,我终于确定了一个简单的解决方案,该解决方案使用全局变量跟踪 UI 状态。

// Globals
const maxQuestions = 10
let verbs
let questionCount = 0
let currentVerb
let uiState

// Read JSON file from server
function getData(url) {
    return fetch(url).then(response => {
        return response.json()
    })
}

// Main entry point
async function main() {
    // Get verbs list
    verbs = await getData('js/verbs-list.json')
    document.getElementById('button').addEventListener('click', onClick)
    newGame()
}

// Handle button click based on UI state
function onClick() {
    switch(uiState) {
        case 'start':
            newGame()
            break;
        case 'check':
            checkVerb()
            break;
        case 'next':
            nextVerb()
            break;
        default:
            throw 'Undefined game state.'
    }
}

// Update the UI state and the button text
function setUIState(state) {
    uiState = state
    document.getElementById('button').value = uiState
}

// Initialize game data
function newGame() {
    score = 0
    // etc
    nextVerb()
}

function nextVerb() {
    // Display the next random verb
    currentVerb = verbs.splice(Math.floor(Math.random() * verbs.length), 1)[0]
    // etc
    setUIState('check')
}

function checkVerb() {
    // Do all checking and UI updating
    if (questionCount < maxQuestions) {
        setUIState('next')
    } else {
        gameOver()
    }
}

function gameOver() {
    // Display end of round information
    setUIState('start')
}

main()

按钮上只有一个eventListener。代码根据 uiState 上下文对该事件作出反应。循环使用简单的变量增量而不是 forwhile.