SetTimeout ดำเนินการเร็วกว่าที่คาดไว้

ฉันกำลังเรียนรู้จาวาสคริปต์:

ขณะนี้ฉันกำลังพยายามสร้างเกมที่สัตว์จะวางไข่แบบสุ่มแต่ละครั้ง ระหว่างหนึ่งถึงสองวินาที จากนั้นเราจะคลิกไปที่พวกมันและทำให้มันหายไป

ฉันกำลังเผชิญกับปัญหาเนื่องจากฉันได้เพิ่ม SetTimeout เพื่อรอจนกว่าแอนิเมชั่นการกระโดดสิ้นสุดลง เพื่อลบคลาส CSS เพื่อลบสัตว์

อย่างไรก็ตาม สัตว์เหล่านี้แสดงเฉพาะครั้งแรกเท่านั้น จากนั้น การดำเนินการของฟังก์ชันกระโดดดำเนินไปอย่างรวดเร็ว เพื่อไม่ให้สัตว์ปรากฏขึ้น

ฉันได้พยายามที่จะดีบักโปรแกรม ก่อนอื่นเราจะเห็นสัตว์ใน div เดียวกันซึ่งได้รับการคำนวณแบบสุ่ม:

ป้อนคำอธิบายรูปภาพที่นี่

ในขั้นตอนถัดไป เราจะเห็นว่าสัตว์นั้นปรากฏขึ้นบนหลุมที่สอง และผู้ดีบักเกอร์ยังคงบันทึกว่าหลุมที่ใช้นั้นเป็นหลุมที่ห้า:

ป้อนคำอธิบายรูปภาพที่นี่

อยู่ในขั้นตอนต่อไปเมื่อเราเห็นว่ามีการใช้ div 2:

ป้อนคำอธิบายรูปภาพที่นี่

และอีกครั้งดีบักเกอร์ยังคงบอกว่าเป็น div 2 ที่ถูกใช้เมื่อสัตว์วางไข่จาก 1:

ป้อนคำอธิบายรูปภาพที่นี่

นอกจากนี้หากฉันพยายามรันโปรแกรมด้วยดีบักเกอร์ สัตว์ต่างๆ จะแสดงเฉพาะครั้งแรก จากนั้นพวกมันก็จะซ่อนอยู่เสมอ นอกจากนี้ในคอนโซลเรายังเห็น console.log จำนวนมากพร้อมเวลาและรูในแต่ละวินาทีในคราวเดียว

ป้อนคำอธิบายรูปภาพที่นี่

นี่คือรหัสปัจจุบัน:

ดัชนี.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Whack A Mole!</title>
    <link href='https://fonts.googleapis.com/css?family=Amatic+SC:400,700' rel='stylesheet' type='text/css'>
    <link rel="stylesheet" href="/thstyle.css">
</head>
<body>

<h1>Whack-a-mole! <span class="score">0</span></h1>
<button onClick="startGame()">Start!</button>

<div class="game">
    <div class="hole hole1">
        <div class="mole"></div>
    </div>
    <div class="hole hole2">
        <div class="mole"></div>
    </div>
    <div class="hole hole3">
        <div class="mole"></div>
    </div>
    <div class="hole hole4">
        <div class="mole"></div>
    </div>
    <div class="hole hole5">
        <div class="mole"></div>
    </div>
    <div class="hole hole6">
        <div class="mole"></div>
    </div>
</div>

<script>
    const holes = document.querySelectorAll('.hole');
    const scoreBoard = document.querySelector('.score');
    const moles = document.querySelectorAll('.mole');

    let lastHole;

    function randomTime(min, max) {
        const time = Math.round(Math.random() * (max - min) + min);
        console.log(time);
    }

    function randomHole(holes) {
        const index = Math.floor(Math.random() * holes.length);
        const hole = holes[index];
        if (hole === lastHole) {
            console.log('We calculate a new hole!');
            return randomHole(holes);
        }
        lastHole = hole;
        return hole;
    }

    function jump() {
        let time = randomTime(1000, 2000);
        let hole = randomHole(holes);
        console.log(time, hole);
        hole.classList.add('up');
        setTimeout(() => {
            hole.classList.remove('up');
            debugger;
            jump();
        }, time);
    }

    jump();

</script>
</body>
</html>

ฉันคิดว่ามันเป็นฟังก์ชัน jump() ที่ทำงานโดยไม่คาดคิด แต่เราจะวางไข่สัตว์ รอการเปลี่ยนแปลง และซ่อนมัน แล้วสร้างสัตว์ใหม่ปรากฏขึ้นได้อย่างไร

สไตล์.css

html {
  box-sizing: border-box;
  font-size: 10px;
  background: #ffc600;
}

*, *:before, *:after {
  box-sizing: inherit;
}

body {
  padding: 0;
  margin:0;
  font-family: 'Amatic SC', cursive;
}

h1 {
  text-align: center;
  font-size: 10rem;
  line-height:1;
  margin-bottom: 0;
}

.score {
  background:rgba(255,255,255,0.2);
  padding:0 3rem;
  line-height:1;
  border-radius:1rem;
}

.game {
  width:600px;
  height:400px;
  display:flex;
  flex-wrap:wrap;
  margin:0 auto;
}

.hole {
  flex: 1 0 33.33%;
  overflow: hidden;
  position: relative;
}

.hole:after {
  display: block;
  background: url(dirt.svg) bottom center no-repeat;
  background-size:contain;
  content:'';
  width: 100%;
  height:70px;
  position: absolute;
  z-index: 2;
  bottom:-30px;
}

.mole {
  background:url('mole.svg') bottom center no-repeat;
  background-size:60%;
  position: absolute;
  top: 100%;
  width: 100%;
  height: 100%;
  transition:all 0.4s;
}

.hole.up .mole {
  top:0;
}

แก้ไข:

ขอบคุณ @Scott Marcus สำหรับความช่วยเหลือของคุณ ฉันซาบซึ้ง

ฉันได้ลองใช้โค้ดต่อไปนี้แล้ว ฉันจะใส่ฟังก์ชัน Jump:

<script>
    const holes = document.querySelectorAll('.hole');
    const scoreBoard = document.querySelector('.score');
    const moles = document.querySelectorAll('.mole');

    let lastHole;
    let timer = null;

    function randomTime(min, max) {
        const time = Math.round(Math.random() * (max - min) + min);
        console.log(time);
    }

    function randomHole(holes) {
        const index = Math.floor(Math.random() * holes.length);
        const hole = holes[index];
        if (hole === lastHole) {
            console.log('We calculate a new hole!');
            return randomHole(holes);
        }
        lastHole = hole;
        return hole;
    }

    function jump() {
        clearTimeout(timer);
        let time = randomTime(1000, 2000);
        let hole = randomHole(holes);
        console.log(time, hole);
        hole.classList.add('up');
        timer = setTimeout(() => {
            hole.classList.remove('up');
            debugger;
            jump();
        }, time);
    }

    jump();

</script>

ฉันได้ค้นพบว่าถ้าเราเลื่อนด้วยล้อเลื่อนของเมาส์ สัตว์ต่างๆ จะวางไข่ใน Opera:

ป้อนคำอธิบายรูปภาพที่นี่

อย่างไรก็ตาม เราไม่ได้ใส่ eventListener ไว้ในสคริปต์

นอกจากนี้ใน Mozilla พวกเขาจะไม่แสดงเลย:

ป้อนคำอธิบายรูปภาพที่นี่

แม้ว่าเราจะเห็นในคอนโซลว่ามีการคำนวณและแสดง RandomTime และ RandomHole อย่างไร

เราจะทำให้สัตว์วางไข่ได้อย่างไร?

ฉันคิดว่าสาเหตุที่มันไม่แสดงเป็นเพราะตัวจับเวลาดำเนินการเร็วเกินไป แม้ว่าเราจะบันทึกตัวสุดท้าย ตัวจับเวลาสุดท้าย และล้างมันหากยังไม่เสร็จสิ้น

ขอขอบคุณสำหรับความช่วยเหลือของคุณ!!


person Enoy    schedule 08.04.2018    source แหล่งที่มา


คำตอบ (1)


เนื่องจากฟังก์ชัน jump ของคุณมี setTimeout และฟังก์ชันการเรียกกลับของตัวจับเวลาทำการเรียกซ้ำไปที่ jump คุณจึงมีสถานการณ์ที่การเรียกครั้งที่สองไปยัง jump สามารถเกิดขึ้นก่อนที่ตัวจับเวลาแรกจะทำงานหรือเสร็จสิ้น ซึ่งอาจทำให้ฟังก์ชันการเรียกกลับของตัวจับเวลาหลายฟังก์ชัน "ซ้อนกัน" ในคิวเหตุการณ์และรันหนึ่งรายการต่อจากอีกฟังก์ชันหนึ่งทันที นี่อาจเป็นพฤติกรรมที่คุณเห็น

คุณต้องตรวจสอบให้แน่ใจว่าการโทรเพิ่มเติมไปยัง jump ไม่ได้เริ่มต้นตัวจับเวลาเพิ่มเติม ซึ่งอาจก่อให้เกิดผลกระทบที่คุณอธิบายได้

คุณบรรลุเป้าหมายนี้ได้โดยทำให้แน่ใจว่าตัวจับเวลาสามารถทำงานได้ครั้งละตัวเท่านั้น และทำได้โดยการจับตัวระบุเฉพาะของตัวจับเวลา และอย่าลืมยกเลิกตัวจับเวลาสุดท้ายก่อนที่จะเริ่มตัวจับเวลาใหม่

var timer = null; // This will hold the most recent timer's id

function jump() {
    clearTimeout(timer); // Cancel any previous timer

    let time = randomTime(1000, 2000);
    let hole = randomHole(holes);
    console.log(time, hole);
    hole.classList.add('up');

    // Capture a reference to the most recent timer
    timer = setTimeout(() => {
        hole.classList.remove('up');
        debugger;
        jump();
    }, time);
}
person Scott Marcus    schedule 08.04.2018
comment
ขอบคุณ @Scott Marcus สำหรับความช่วยเหลือของคุณ ฉันได้พยายามจัดเก็บตัวจับเวลาปัจจุบันในตัวแปรแล้วใช้ crearTimeout เป็นคำสั่งของการกระโดดครั้งแรก อย่างไรก็ตาม พวกมันไม่แสดงใน Mozilla เลย และใน Opera พวกมันจะแสดงเฉพาะในการดำเนินการครั้งแรกเท่านั้น จากนั้นหลังจากที่อันแรกหายไป ตัวอื่น ๆ จะไม่แสดง ขึ้น. - person Enoy; 08.04.2018
comment
@Enoy จริงๆ แล้วฉันไม่ได้ดูรหัสทั้งหมดของคุณ คุณน่าจะยังคงมีปัญหากับด้านอื่นๆ ของโค้ดอยู่ แต่สิ่งที่ฉันได้แชร์ไว้ที่นี่จะกล่าวถึงการเร่งความเร็วของตัวจับเวลาที่คุณถาม - person Scott Marcus; 08.04.2018