- 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
배열 안의 수를 오름차순으로 정렬한다면 전체 숫자를 쭉 훑어보면서 가장 작은 숫자를 하나 가져오고, 다시 전체를 훑어보다가 그 다음 작은 숫자를 가져오고 반복할 것이다.
이를 선택 정렬 알고리즘이라고 하며, 가장 효율적인 방식이 아니다.
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
메소드처럼 함수에 적힌 규칙에 따라 배열이 정렬되는 함수를 비교 함수라고 한다.
a
와 b
가 주어질 때 반환값에 따라 배열이 다르게 정렬된다. 현재는 비교 함수의 반환 값이a-b
이기 때문에 이 값이 0
보다 크면 b,a
순서로, 0
보다 작으면 a,b
순서로 정렬된다. 0
이면 순서가 유지된다.
splice().sort()
:splice
를 이용해 새로운 배열을 만들어sort
를 하는 것에 주목해야 한다.
sort
는 배열의 각 요소를 비교하여 재배열하며, 이 과정에서 배열의 메모리 참조를 유지한다.
배열의 참조를 유지하기 때문에,sort
메서드는 원본 배열을 변경(mutate)한다.
- 이에 따라
slice
한 값을winBalls
에 대입하는 것이다.
setTimeout(() => {
// 내용
}, 밀리초);
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
라는 새로운 태그를 생성한다.자바스크립트는 기본적으로 한 번에 한가지 일만 할 수 있다.
- 타이머의 시간은 아쉽게도 정확하지 않다.
- 비동기 함수인
setTimeout
안에 있는 함수는 기존 작업들이 모두 끝나야 실행된다.
타이머와 관련된 자바스크립트 이벤트 루프 개념 참고1
타이머와 관련된 자바스크립트 이벤트 루프 개념 참고2
7개의 공을 모두 화면에 표시하기
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
은 블록 스코프
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문
안에 들어있으면 바깥에서 접근할 수 있다. ⭐️ if (true) {
let a = 1;
}
console.log(a); // Uncaught ReferenceError: a is not defined
let
은 블록 스코프이기 때문에 블록 안에서 정의된 것은 블록 안에서만 접근할 수 있다.if
, for
, while
및 함수 내 {}
를 의미한다.const
도 let
과 마찬가지로 블록 스코프를 가진다.앞서 작성한 코드를 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
를 콘솔로 출력하면 모두undefined
및6
로 출력된다.
setTimeout
의 콜백 함수 안에 든 i
와 바깥의 1000 * (i+1)
는 다른 시점에 실행된다.
1000 * (i + 1)
는 반복문을 돌 때 실행setTimeout
의 콜백 함수는 지정한 시간 뒤에 호출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
콜백 함수 내부의i
도setTimeout
을 호출할 때의i
와 같은 값이 들어간다.setTimeout
같은 비동기 함수와 반복문이var
를 만나면 이런 문제가 발생한다.⭐️