
최근 모던 자바스크립트를 다시 공부하는 중이다. ES6 이후의 자바스크립트는 예전에 듬성듬성 공부했어서 이번에 배운 내용을 제대로 익혀야 한다. 이럴 때는 토이 프로젝트가 좋다.
반응속도 테스터는 구현하기 쉽다. 비동기의 개념과 관련 모듈만 알면 바로 구현 가능하다. 그런데 상품성은 이런 낮은 구현 난이도에 비해 높다. 테스트 시간이 매우 짧고 단순하며 경쟁성이 있다보니, 자연스럽게 여러번 사용해보게 된다.
사용자가 자발적으로 '여러번 사용'해준다니! 바로 구현하러 가자.
정말 단순한 프로그램이니 클래스는 하나만 구현할 것이다. 클래스 이름은 간단하게 Test로 명명하자.
Test 클래스가 해야 할 일, 즉 메소드는 다음과 같다.
static void Test:ready() : 테스트를 준비한다.
Test.start 메소드를 추가한다.Test 클래스의 메소드도 이런 식으로 이벤트 리스너로 추가할 거라, 메소드를 static으로 선언하는 게 편하다 👉 prototype 안 씀)static void Test:start() : 테스트를 시작한다.
Test.start)를 삭제하고, Test.end의 익명 함수를 새로운 이벤트 리스너로 추가한다. 이때 once 옵션을 이용해 자동으로 익명 함수가 삭제되도록 한다. (👉 귀찮은 익명 함수 삭제 안 해도 됨)Test.end 에는 비동기 타이머 주소값과 테스트 시작 시간을 인자로 넘겨준다.static void Test:end(testStartTime, timeOut) : 테스트를 끝내고 결과와 함께 '다시 하기' 버튼을 띄운다.
testStartTime이 초기화되지 않았다면, 테스트 시작 전에 종료한 것으로 처리하고, 초기화 되었다면 테스트 결과를 출력한다.Test.start를 이벤트 리스너로 추가한다. <head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://www.w3schools.com/w3css/4/w3.css">
<title>Reaction Speed Test</title>
</head>
<body>
<div id = "game">
<div id = "textBox"></div>
<button class="w3-button w3-large w3-black" id = "gameButton"></button>
</div>
</body>
#game {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width : 500px;
height : 200px;
text-align : center;
padding-top : 40px;
border : 2px solid black;
}
#textBox {
font-size : 2em;
text-align : center;
margin-bottom : 0.5em;
font-weight : bold;
}
const textBox = document.getElementById("textBox");
const gameButton = document.getElementById("gameButton");
const game = document.getElementById("game");
const READY_SCREEN_COLOR = "green";
const GAME_SCREEN_COLOR = "red";
이벤트 리스너를 추가할 때, 버튼 인스턴스에 Test 의 메소드를 전달한다. 따라서 this를 이용하면 this의 대상이 Test 였다가 버튼 인스턴스로 바뀌기 때문에 괜히 코드가 복잡해진다. 게다가 우리는 Test 의 메소드를 전부 static으로 설정할 것이라 Test 인스턴스를 생성하지 않는다. 그러니 global 변수로 선언하자.
class Test {
static ready(){
//배경을 🟩으로 바꾼다.
game.style.backgroundColor = READY_SCREEN_COLOR;
textBox.textContent = "Reaction Speed Test ⏲";
gameButton.textContent = "Start!";
//버튼에 클릭 타입 이벤트 리스너로 `Test.start` 메소드를 추가한다.
gameButton.addEventListener("click", Test.start);
};
static start(){
textBox.textContent = "if 🟥, press button ❗❗";
gameButton.textContent = "Red!";
let randFloat = (Math.random() * 2 + 2) * 1000;
let testStartTime;
// 비동기 타이머를 설정하고 주소값을 저장한다.
let timeOut = window.setTimeout(function() {
// 설정된 타이머는 시간이 되면
// (1) 배경을 🟥으로 바꾸고,
game.style.backgroundColor = GAME_SCREEN_COLOR;
// (2) 테스트 시작 시간을 micro second 단위로 저장한다.
testStartTime = window.performance.now();
}, randFloat);
// 기존에 설정된 클릭 타입 이벤트 리스너(`Test.start`)를 삭제하고,
gameButton.removeEventListener("click", Test.start);
// `Test.end`를 새로운 이벤트 리스너로 추가한다.
// `Test.end` 에는 비동기 타이머 주소값과 테스트 시작 시간을 인자로 넘겨준다.
gameButton.addEventListener("click",
() => Test.end(testStartTime, timeOut),
// 이때 once 옵션을 이용해 자동으로 익명 함수가 삭제되도록 한다
{once : true});
};
static end(testStartTime, timeOut){
// `testStartTime`이 초기화되지 않았다면,
if (testStartTime == null){
// 테스트 시작 전에 종료한 것으로 처리하고
Test.printMessage(`Too early! 😅`);
}
// 초기화 되었다면 테스트 결과를 출력한다.
else {
Test.printMessage(`⏲ : ${Math.floor(window.performance.now() - testStartTime)} ms`);
}
// 비동기 타이머를 해제한다.
window.clearTimeout(timeOut);
gameButton.textContent = "Try again?";
// `Test.start`를 새로운 이벤트 리스너로 추가한다.
gameButton.addEventListener("click", Test.start);
};
static printMessage(message){
textBox.textContent = message;
// 배경을 🟩으로 바꾸고,
game.style.backgroundColor = READY_SCREEN_COLOR;
};
}
Test.ready();
Test.end는 익명 함수를 리스너로 쓰기 때문에 이벤트 리스너를 지우기 귀찮다. 이런 경우 보통 익명 함수를 변수 선언을 하거나 Callback을 이용해서 처리를 해왔는데, addEventListener API 문서를 읽다가 once 옵션이 있는 걸 알았다. 덕분에 쉽게 익명 함수 리스너를 지울 수 있었다.