JavaScript代码执行时机异常:console.log(results)提前运行的原因咨询
JavaScript代码执行时机异常:console.log(results)提前运行的原因咨询
我遇到了一个代码执行时机的问题,我的JavaScript里有些代码(比如console.log(results))会在不该运行的时候提前执行。我本来想让随机数生成和结果对比只在用户点击按钮后才触发,所以特意把随机数生成的逻辑放到了switch语句里,但还是出现了提前运行的情况,这是为什么呢?
下面是我的完整页面代码:
HTML代码
<!DOCTYPE html> <html lang="en"> <head> <link rel="stylesheet" href="style.css"> <script src="script.js" defer></script> <meta charset="UTF-8"> <title>rock paper scissors</title> <link rel="icon" href="rps-smiling-icon.svg"> <style> </style> </head> <body> <div id="header"> <img alt="smiling face" src="rps-smiling.svg" id="face" onmousedown=""> </div> <div id="playground"> <div id="results"> <p id="player">???</p> <p>|</p> <p id="computer">???</p> </div> <div id="options"> <div> <button id="Rock">Rock</button> </div> <div> <button id="Paper">Paper</button> </div> <div> <button id="Scissors">Scissors</button> </div> </div> </div> </body> </html>
CSS代码
@import url('https://fonts.googleapis.com/css2?family=Pixelify+Sans:wght@400..700&display=swap'); body, html { height: 100%; background-color: #c0c0c0; margin: 20px; } #header { display: flex; justify-content: center; border-top: #808080 4px solid; border-left: #808080 4px solid; border-right: white 4px solid; border-bottom: white 4px solid; } img { margin: 10px; } #playground { display: flex; flex-direction: column; justify-content: space-between; height: 75%; margin: 15px 0; border-top: #808080 4px solid; border-left: #808080 4px solid; border-right: white 4px solid; border-bottom: white 4px solid; } #results, #options { display: flex; flex-direction: row; justify-content: center; } #options { justify-content: space-around; } p { font-family: "Pixelify Sans", serif; font-size: 100px; margin: 0; } #options>* { border-top: #808080 4px solid; border-left: #808080 4px solid; border-right: #808080 4px solid; border-bottom: #808080 4px solid; margin: 0 20px 20px; } div>button { font-family: "Pixelify Sans", serif; font-size: 30px; height: 90px; width: 150px; border-top: white 4px solid; border-left: white 4px solid; border-right: #808080 4px solid; border-bottom: #808080 4px solid; border-radius: 0; }
JavaScript代码
const player = document.querySelector("#player") const computer = document.querySelector("#computer") const body = document.querySelector("body") const face = document.querySelector("#face") const options = document.querySelector("#options") options.addEventListener("mousedown", (e) => { let target = e.target if (target.nodeName === "BUTTON") { face.setAttribute("src", "rps-cautious.svg") } }) body.addEventListener("mouseup", (e) => { face.setAttribute("src", "rps-smiling.svg") }) let userInput let computerChoice let results options.addEventListener("click", (e) => { let target = e.target; switch (target.id) { case "Rock": userInput = 0; player.textContent = "Rock"; computerInput() results = (compare(userInput, computerChoice)) break; case "Paper": userInput = 1; player.textContent = "Paper"; computerInput() results = (compare(userInput, computerChoice)) break; case "Scissors": userInput = 2; player.textContent = "Scissors"; computerInput() results = (compare(userInput, computerChoice)) break; } }, { once: true }) console.log(results) function computerInput() { computerChoice = Math.floor(Math.random() * 3); if (computerChoice === 0) { computer.textContent = "Rock" } else if (computerChoice === 1) { computer.textContent = "Paper" } else { computer.textContent = "Scissors" } return computerChoice } function compare(userInput, computerInput) { if (userInput !== computerInput) { if (userInput + computerInput !== 2) { if (userInput > computerInput) { return 0 } else { return 1 } } else if (userInput > computerInput) { return 1 } else { return 0 } } else { return 2 } }
问题原因分析
你遇到的核心问题是代码执行顺序的问题:
- 你的
console.log(results)是写在全局作用域里的,当页面加载完成、JS脚本执行时,这行代码会立刻运行——这时候用户还没点击任何按钮,results变量只是被声明了但还没被赋值,所以控制台会输出undefined。 - 而
click事件监听里的逻辑是异步触发的,只有当用户实际点击按钮时才会执行,给results赋值,但这时候console.log早就跑完了。
另外,你给click事件加了{ once: true }选项,这意味着这个事件监听器只会触发一次——用户点击一次按钮后,后续的点击就不会再执行里面的逻辑了,这可能也不符合你“可以多次玩游戏”的预期。
解决方案
把
console.log(results)移到click事件内部:
这样只有当用户点击按钮、results被赋值后,才会执行打印操作。修改后的click事件代码如下:options.addEventListener("click", (e) => { let target = e.target; switch (target.id) { case "Rock": userInput = 0; player.textContent = "Rock"; computerInput() results = compare(userInput, computerChoice) break; case "Paper": userInput = 1; player.textContent = "Paper"; computerInput() results = compare(userInput, computerChoice) break; case "Scissors": userInput = 2; player.textContent = "Scissors"; computerInput() results = compare(userInput, computerChoice) break; } console.log(results) // 移到这里,确保赋值后再打印 })移除
{ once: true }选项:
如果你希望用户可以多次玩游戏,去掉这个选项,这样每次点击按钮都会触发事件逻辑。(可选)优化
compare函数的可读性:
现在的compare函数逻辑有点绕,你可以简化成更直观的判断,比如:function compare(userInput, computerInput) { // 平局 if (userInput === computerInput) return 2; // 用户赢的情况:石头赢剪刀,剪刀赢布,布赢石头 if ( (userInput === 0 && computerInput === 2) || (userInput === 2 && computerInput === 1) || (userInput === 1 && computerInput === 0) ) { return 0; } // 剩下的情况就是电脑赢 return 1; }
这样修改后,就能保证只有用户点击按钮后才会生成随机数、对比结果,并且正确打印出结果啦~
备注:内容来源于stack exchange,提问作者noxar_ad




