首页 > 解决方案 > 如何使用 setTimeout 延迟代码执行?

问题描述

我仍然是一个 JS 初学者,试图掌握 jQuery 并开始将 Simon Game 编码为一个项目。您有 4 个彩色空间,它们应该以随机顺序点亮并播放声音,从长度为 1 的序列开始。玩家通过单击方块重新创建该序列。如果成功,随机序列长度将增加 1,如果你犯了错误,你必须重新开始。

我正处于简单地创建长度为 5 的随机序列并直观地显示该序列的阶段。这就是我想出的:

var sequence = [];

$("button").click(startGame);

function startGame() {
  for (let index = 0; index < 5; index++) {
    sequence.push(nextColour());
    touchField(index);
    // playSound();
  }
}

function nextColour() {
  var randomNumber = Math.floor(Math.random() * 4) + 1;
  if (randomNumber === 1) {
    var nextColour = "red";
  } else if (randomNumber === 2) {
    var nextColour = "blue";
  } else if (randomNumber === 3) {
    var nextColour = "green";
  } else {
    var nextColour = "yellow";
  }
  return nextColour
};

function touchField(index) {
  $("." + sequence[index]).addClass("active");
  setTimeout(function() {
    $("." + sequence[index]).removeClass("active")
  }, 300);
}
.active {
  border: 1rem solid purple;
}

.red {
  border: 1px solid red;
}

.blue {
  border: 1px solid blue;
}

.green {
  border: 1px solid green;
}

.yellow {
  border: 1px solid yellow;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<img class="red" src="https://via.placeholder.com/50" alt="">
<img class="blue" src="https://via.placeholder.com/50" alt="">
<img class="green" src="https://via.placeholder.com/50" alt="">
<img class="yellow" src="https://via.placeholder.com/50" alt="">

所以问题是,“.active”类几乎同时从所有方块中添加和删除——这使得无法区分任何类型的序列。所以我想我可以使用“setTimeout();”。但这并不妨碍它这样做。我相信发生的事情是,直到确实执行超时后要评估的表达式,代码才会继续运行,因此几乎同时添加“.active”和超时 5 次 - 这导致它们几乎同时删除。理想情况下,在删除“.active”和将“.active”添加到下一个方块之间有足够的时间。我还尝试将 setTimeout 添加到“touchSquare()”函数调用中。它也不起作用 - 我相信出于同样的原因,它只是继续执行。我希望你能看到我的问题。^^

我看过其他问题,但答案似乎忽略了浏览器在到达 setTimeout 后继续执行代码的事实,例如这里:

如何在我的javascript中延迟执行

如果您将其复制粘贴到控制台中,它根本不会做它应该做的事情,因为在识别 setTimeout 之后代码会继续执行。我希望我能让我的问题对你有意义,这是我第一次在 StackOverflow 上发布问题。如果我能以任何方式改进我提出问题的方式,我非常感谢建设性的批评!

标签: javascripthtmljquery

解决方案


要了解它似乎忽略setTimeout()和 事情似乎同时发生的原因,您需要了解 和 之间的stack区别event loop

并发模型和事件循环

放入堆栈的事情会立即(或尽快)发生,这就是for循环正在做的事情。

放入事件循环的事情发生在给定的最短时间之后(之后,不完全是),这就是setTimeout正在做的事情。

换句话说,for 循环将 setTimouts 放入事件循环中,彼此间隔毫秒,因此超时会在彼此间隔毫秒内触发。事件循环不像这样做,然后这个,然后那个。它的工作方式类似于“此事件是否达到或超过了它的超时时间?如果是,请尽快执行”

为了达到您正在寻找的效果,您需要一些递归,或者当一个函数调用自身时。这样TouchField(),在前一个完成之前不会调用下一个,从而按照您期望的方式将事物隔开。

我做了一些非初学者的事情来简化随机序列的生成(使用Array.from()和一个值数组而不是冗长的 if/else 块),并且我添加了第二个超时以便有一点显示字段之间的延迟,(否则同一字段连续两次模糊在一起),但希望这有助于您理解这个概念。

let level = 5, sequence; //increment the level /sequence length as the player progresses

$('button').click(function startGame() {
    sequence = Array.from({ length: level },
      field => ['red','blue','green','yellow'][Math.floor(Math.random() * 4)]);
    
    TouchField(); //no argument to start recursion / show sequence
});

function TouchField(index = 0) {
  if (index <= sequence.length - 1) { //recursion exit condition
    // playSound();
    $('.' + sequence[index]).addClass('active');

    setTimeout(function() {
      $('img').removeClass('active');
      
      setTimeout(function() {
        TouchField(index + 1); //recursion
      }, 500);
    }, 500);
  }
}
.active {
  border: 1rem solid purple !important;
}

.red {
  border: 1px solid red;
}

.blue {
  border: 1px solid blue;
}

.green {
  border: 1px solid green;
}

.yellow {
  border: 1px solid yellow;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<img class="red" src="https://via.placeholder.com/50" alt="">
<img class="blue" src="https://via.placeholder.com/50" alt="">
<img class="green" src="https://via.placeholder.com/50" alt="">
<img class="yellow" src="https://via.placeholder.com/50" alt="">
<div>
  <button>PLAY</button>
</div>


推荐阅读