如何使用setTimeout延迟代码执行



我还是一个JS初学者,试图掌握jQuery,并开始将Simon游戏作为一个项目进行编码。你有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="">

所以问题是,那个阶级"。活动的";几乎同时从所有方块中添加和删除,这使得无法区分任何类型的序列。所以我想我可以用";setTimeout()&";。但这并不妨碍它这么做。我相信,在超时后要计算的表达式真正执行之前,代码只是继续运行,从而添加";。活动的";并且几乎同时超时5次,这导致它们几乎同时被移除。理想情况下,在移除"之间有足够的时间;。活动的";并添加";。活动的";到下一个广场。我还尝试将setTimeout添加到";touchSquare()";函数调用。它也不起作用——我相信出于同样的原因,它只是继续执行。我希望你能看到我的问题。^^

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

如何在我的javascript 中延迟执行

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

一种方法是将setTimeout用作异步循环,即回调将使用重复的setTimeout调用来调度新计时器。但是,当你想在结束后继续其他东西时,你很快就会进入所谓的";回调地狱";。

我建议你加入承诺。JavaScript具有asyncawait语法,以简化promise编程。

因此,首先要做的是创建等效的setTimeout,但作为一个返回promise的函数,您可以创建await

然后,代码的其余部分几乎不需要更改。

然而,我建议:

  • 将索引存储在sequence数组中,而不是颜色名称
  • 在开始时将对图像的引用放在jQuery集合中
  • 在动画过程中隐藏"开始"按钮--您不希望用户干扰该动画
  • 确保每次调用startGame时重置sequence阵列

以下是它的工作原理。忽略我添加的额外CSS,因为我无法访问您的图像。

const sequence = [];
// Load the elements in an node list
const $images = $(".red, .blue, .green, .yellow");
$("button").click(startGame);
// Helper function
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
async function startGame() {
$("button").hide(); // Don't allow a click on Start while the sequence is animating
sequence.length = 0; // reset
for (let index = 0; index < 5; index++) {
sequence.push(nextColour());
await touchField(sequence[index]);
}
$("button").show();
console.log(sequence);
// Continue with other logic here...
}
function nextColour() {
return Math.floor(Math.random() * 4);
};
async function touchField(index) {
$images.eq(index).addClass("active");
await delay(300);
$images.eq(index).removeClass("active");
await delay(100);
}
.active {
border: 1rem solid purple;
}
.red    { background-color: red;    }
.blue   { background-color: blue;   }
.green  { background-color: green;  }
.yellow { background-color: yellow; }
img {
display: inline-block;
width: 70px;
height: 50px;
border: 1rem solid white;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<img class="red" src="/projects/simon-game/images/red.png" alt="">
<img class="blue" src="/projects/simon-game/images/blue.png" alt="">
<img class="green" src="/projects/simon-game/images/green.png" alt="">
<img class="yellow" src="/projects/simon-game/images/yellow.png" alt="">
<p>
<button>Start</button>

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

并发模型与事件循环

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

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

换言之,for循环将setTimouts放入事件循环中的时间间隔在毫秒内,因此超时在毫秒内触发。事件循环的工作方式与do this、thenthis、then[/strong>that不同。它就像一个";此事件是否达到或超过了超时时间?如果是,则尽快执行";

为了达到您想要的效果,您需要一些递归,或者当函数调用自己时。这样,下一个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>

最新更新