[Let's get it 자바스크립트 프로그래밍] - 6.1~6.6 타이머 사용하기_로또 추첨기

신혜린·2025년 1월 19일
0
post-thumbnail

순서도 및 UI 그리기

  • 1~45까지의 숫자를 준비한다
  • 숫자를 섞는다
  • 공 7개를 뽑는다
  • 마지막 공은 보너스 공이 된다
  • 1초마다 공을 하나씩 화면에 표시한다
<!DOCTYPE html>
<head>
  <meta charset="utf-8">
  <title>로또추첨기</title>
  <style>
    .ball {
      display: inline-block;
      border: 1px solid black;
      border-radius: 20px;
      width: 40px;
      height: 40px;
      line-height: 40px;
      font-size: 20px;
      text-align: center;
      margin-right: 20px;
    }
  </style>
</head>

<body>
<div id="result">추첨 결과는? </div>
<div id="bonus">보너스: </div>
<script>
</script>
</body>

무작위로 공 뽑기

<script>
  const candidate = Array(45).fill().map( (v,i) => i+1 );
</script>

전체 공의 개수를 45개로 설정

  • 반복문을 사용하지 않고 배열의 메서드만을 사용해 뽑는다.

  const candidate = Array(45).fill().map((v, i) => i + 1); 
  const shuffle = [];
while(candidate.length > 0) {
  const random = Math.floor(Math.random() * candidate.length);
  const spliceArray = candidate.splice(random, 1);
  const value = spliceArray[0];
  shuffle.push(value);
}

피셔-예이츠 셔플 알고리즘(Fisher-Yates Shuffle Argothim)
빈 배열 shuffle을 만든 후, candidate 배열의 요소를 무작위로 뽑아 shuffle 배열로 하나씩 옮겨준다.

  • candidate 배열의 길이가 0이 될 때까지 반복(candidate.length > 0)하므로 while문을 사용한다.


공 정렬하기

선택 정렬(selection sort) 알고리즘
사람이 shuffle 배열 안의 수를 오름차순으로 정렬한다면 전체 숫자를 쭉 훑어보면서 가장 작은 숫자를 하나 가져오고, 다시 전체를 훑어보다가 그 다음 작은 숫자를 가져오고 반복할 것이다.
이를 선택 정렬 알고리즘이라고 하며, 가장 효율적인 방식이 아니다.

sort 메소드

const winBalls = shuffle.slice(0, 6).sort((a, b) => a - b);
const bonus = shuffle[6];
console.log(winBalls, bonus);

shuffle 배열의 앞에서 6개 숫자만 뽑아낸 후 이를 오름차순으로 정렬해 winBalls 배열에 저장한다.
보너스 공인 shuffle의 6번 인덱스(가장 마지막 숫자)를 따로 뽑아 bonus 변수에 저장한다.

sort 메소드처럼 함수에 적힌 규칙에 따라 배열이 정렬되는 함수를 비교 함수라고 한다.

  • 비교 함수의 매개변수로 ab가 주어질 때 반환값에 따라 배열이 다르게 정렬된다. 현재는 비교 함수의 반환 값이a-b이기 때문에 이 값이 0보다 크면 b,a 순서로, 0보다 작으면 a,b 순서로 정렬된다. 0이면 순서가 유지된다.

splice().sort(): splice를 이용해 새로운 배열을 만들어 sort를 하는 것에 주목해야 한다.
sort는 배열의 각 요소를 비교하여 재배열하며, 이 과정에서 배열의 메모리 참조를 유지한다.
배열의 참조를 유지하기 때문에, sort 메서드는 원본 배열을 변경(mutate)한다.

  • 이에 따라 slice 한 값을 winBalls에 대입하는 것이다.


일정 시간 후에 실행하기

setTimeout(() => {
  // 내용
}, 밀리초);
  • 1초에 하나씩 화면에 표시하기 위해 사용하는 메소드 setTimeout비동기 함수이다.
  • setTimeout 안에 넣는 함수는 특정 작업 이후에 추가로 실행되는 함수이므로 콜백 함수로 본다.
const $result = document.querySelector('#result');
setTimeout(() => {
  const $ball = document.createElement('div');
  $ball.className = 'ball';
  $ball.textContent = winBalls[0];
  $result.appendChild($ball);
}, 1000);
  • document.createElement를 통해 HTML에 result라는 새로운 태그를 생성한다.

자바스크립트는 기본적으로 한 번에 한가지 일만 할 수 있다.



타이머와 반복문 같이 사용하기

7개의 공을 모두 화면에 표시하기

  • 6개의 공을 먼저 화면에 표시한다.
  • 그 이후 보너스 공을 표시한다.
console.log(winBalls, bonus);
const $result = document.querySelector('#result');
for (let i = 0; i < winBalls.length; i++) {
  setTimeout(() => {
    const $ball = document.createElement('div');
    $ball.className = 'ball';
    $ball.textContent = winBalls[i];
    $result.appendChild($ball);
  }, 1000 * (i + 1));
}

const $bonus = document.querySelector('#bonus');
setTimeout(() => {
  const $ball = document.createElement('div');
  $ball.className = 'ball';
  $ball.textContent = bonus;
  $bonus.appendChild($ball);
}, 7000);

setTimout이 7번 반복 실행되었다.

  • 반복을 줄이기 위해 따로 함수로 정의해서 사용하는 것이 좋다.
for(let i = 0; i < winBalls.length; i++){
  setTimeout(addBall(winBalls[i],$result) , (i+1) * 1000 );
}

일반 공을 뽑는 코드와 보너스 공을 뽑는 코드가 중복된다. 중복되는 것은 함수로 뽑아낸다. 중복되지 않는 것은 매개변수로 만든다.

const $result = document.querySelector('#result');
function drawBall(number, $parent) {
  const $ball = document.createElement('div');
  $ball.className = 'ball';
  $ball.textContent = number;
  $parent.appendChild($ball);
}

for (let i = 0; i < winBalls.length; i++) {
  setTimeout(() => {
    drawBall(winBalls[i], $result);
  }, 1000 * (i + 1));
}

const $bonus = document.querySelector('#bonus');
setTimeout(() => {
  drawBall(bonus, $bonus);
}, 7000);

var와 let의 차이

스코프(scope) ⭐️

변수는 스코프라는 범위 개념을 가진다.

  • var함수 스코프
  • let블록 스코프

var

function b() {
  var a = 1;
}
console.log(a); // Uncaught ReferenceError: a is not defined
  • a는 함수 안에서 선언된 변수이므로 함수 바깥에서는 접근할 수 없다.
  • 이렇듯 함수를 경계로 접근 여부가 달라지는 것을 함수 스코프 라고 한다.
if (true) {
  var a = 1;
}
console.log(a); // 1
  • var는 함수 스코프이기 때문에 함수만 신경 쓴다.
  • if문 안에 들어있으면 바깥에서 접근할 수 있다. ⭐️

let, const

if (true) {
  let a = 1;
}
console.log(a); // Uncaught ReferenceError: a is not defined
  • let은 블록 스코프이기 때문에 블록 안에서 정의된 것은 블록 안에서만 접근할 수 있다.
  • 블록은 if, for, while및 함수 내 {} 를 의미한다.
  • constlet과 마찬가지로 블록 스코프를 가진다.

테스트

앞서 작성한 코드를 let에서 var 로 변환하여 코드를 작성해본다.

for (var i = 0; i < winBalls.length; i++) {
  setTimeout(() => {
    console.log(winBalls[i], i);
    drawBall(winBalls[i], $result);
  }, 1000 * (i + 1));
}

보너스 공을 빼고 모든 공에 숫자가 뜨지 않을 것이다.

  • winBalls[i]i를 콘솔로 출력하면 모두 undefined6 로 출력된다.

setTimeout의 콜백 함수 안에 든 i와 바깥의 1000 * (i+1)는 다른 시점에 실행된다.

  • 1000 * (i + 1)는 반복문을 돌 때 실행
  • setTimeout의 콜백 함수는 지정한 시간 뒤에 호출
  • 그런데 반복문은 매운 빠른 속도로 돌아서 콜백 함수가 실행될 때는 이미 i가 6이 되어 있는 것이다.

실행순서

i가 0일 때 setTimeout(콜백0, 1000) 실행
i가 1일 때 setTimeout(콜백1, 2000) 실행
i가 2일 때 setTimeout(콜백2, 3000) 실행
i가 3일 때 setTimeout(콜백3, 4000) 실행
i가 4일 때 setTimeout(콜백4, 5000) 실행
i가 5일 때 setTimeout(콜백5, 6000) 실행
i가 6이 됨
1초 후 콜백0 실행(i는 6)
2초 후 콜백1 실행(i는 6)
3초 후 콜백2 실행(i는 6)
4초 후 콜백3 실행(i는 6)
5초 후 콜백4 실행(i는 6)
6초 후 콜백5 실행(i는 6)

반면에 let은 하나의 블록마다 고정된 i값을 갖게 된다.

  • 블록 스코프의 특성이라고 보면 된다.
  • 따라서 setTimeout 콜백 함수 내부의 isetTimeout을 호출할 때의 i와 같은 값이 들어간다.
  • setTimeout 같은 비동기 함수반복문var를 만나면 이런 문제가 발생한다.⭐️
profile
개 발자국 🐾

0개의 댓글