You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多
文档控制台
免费开始使用

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 }选项,这意味着这个事件监听器只会触发一次——用户点击一次按钮后,后续的点击就不会再执行里面的逻辑了,这可能也不符合你“可以多次玩游戏”的预期。

解决方案

  1. 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) // 移到这里,确保赋值后再打印
    })
    
  2. 移除{ once: true }选项
    如果你希望用户可以多次玩游戏,去掉这个选项,这样每次点击按钮都会触发事件逻辑。

  3. (可选)优化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

火山引擎 最新活动