2021.05.12
1. concat()
2. 버그
- 호출 스택, 이벤트 루프, 백그라운드, 태스크 큐
- 버그 해결
3. 느낀 점
기존 배열을 변경하지 않는다!clickable 변수로 막을 수 있다.
let clickable = false;
.
.
.
function onCLickCard() {
if (!clickable) { //clickable이 false면 onClickCard실행 불가.(return으로 아랫줄 실행 막음)
return;
}
.
.
.
}
function startGame() {
clickable = false;
.
.
.
setTimeout(() => {
document.querySelectorAll(".card").forEach((card) => {
card.classList.remove("flipped");
});
clickable = true; //카드를 감출 때 true값으로 변환.
}, 5000);
}
버그2, 버그3: 이미 짝이 맞춰진 카드를 선택해도 카드가 다시 뒤집힌다. / 한 카드를 두 번 연달아 클릭하면 더 이상 그 카드가 클릭되지 않는다.
function onCLickCard() {
if (!clickable || completed.includes(this) ||clicked[0]===this) { //이미 완성된 카드거나 연달아 클릭하는 경우
return;
}
.
.
.
}
setTimeout함수가 실행되면 백그라운드에서 시간을 재고 해당 시간이 되면 setTimeout의 콜백 함수를 태스크 큐로 전달한다. addEventListener로 추가한 이벤트를 저장했다가 이벤트가 발생하면 콜백 함수를 태스크 큐로 보낸다. 백그라운드에서 코드를 실행하는 것이 아니라 실행될 콜백 함수들이 태스크 큐로 들어간다는 것을 명심하자. 태스크 큐에 먼저 들어온 함수부터 실행된다는 것이다. 하지만 태스크 큐도 함수를 직접 실행하지 않는다. 태스크 큐에서 호출 스택으로 함수를 이동시키는 존재다. 호출 스택이 비어 있으면 이벤트 루프는 태스크 큐에서 함수를 순서대로 꺼내어 호출 스택으로 옮긴다. 호출 스택으로 이동한 함수는 그때 실행된다. 실행이 완료된 함수는 호출 스택에서 빠지고 호출 스택이 비어있으면 이벤트 루프는 태스크 큐에 있는 다음 함수를 호출 스택으로 옮긴다.
호출 스택과 이벤트 루프에 영향을 주지 않는다! - 선언은 스코프의 영역이다. - 호출 스택과 이벤트 루프는 함수 호출과 밀접한 관련이 있다. - 자바스크립트 엔진은 자바스크립트 소스 코드가 처음 실행되는 순간(script태그)도 하나의 함수가 실행된다고 판단하고 크롬은 이를 anonymous함수로 표시한다. 참고로 호출 스택을 보고 싶다면 console.trace를 사용하여 콘솔창에 출력가능하다.
그럼 startGame()이 끝나면 호출 스택에서 대기중인 함수는 사실상 없지만 백그라운드에 클릭이벤트와 타이머가 남아있다는 사실! 그러면 대부분의 비동기 코드들이 백그라운드에 있다고 생각하면 될 것 같다.
document.querySelectorAll(".card").forEach((card, index) => {
setTimeout(() => {
card.classList.add("flipped");
}, 1000 + 200 * index);
});
위 코드에서 1초타이머의 콜백함수는 card.classList.add("flipped"); 인데 이 구간이 바로 태스크 큐에 쌓여있고 방금같은 상황에 호출스택이 비어있으니 이벤트루프가 태스크 큐의 해당 함수를 호출 스택으로 전달한다(실행을 하려면 호출 스택으로 쌓여져있어야 하기 때문).
4개를 연속 선택했을 때 연속된 카드를 1,2,3,4번 카드라고 가정해보자.
push()로 실행되어 clicked === [1번 카드] 상태setTimeout이 실행될거고 0.6초 타이머가 백그라운드에 생성되어 대기상태. 여기서 핵심을 알 수 있다. 태스크 큐에 들어온 순서대로 호출 스택으로 가다보니 0.6초 타이머의 콜백함수보다 3번 카드의 클릭 콜백 함수가 먼저 실행되는 것이다.clicked배열에 [1,2]가 들어가 있고 태스크 큐엔 3번4번이 대기중이며, 백그라운드엔 addEventListener(12), setTimeout 600ms가 대기중이다.clicked===[1,2,3] / 태스크 큐: 4번 / 백그라운드: addEventListener(12), setTimeout 600ms, setTimeout 600msclicked===[1,2,3,4] / 태스크 큐: empty / 백그라운드: addEventListener(12), setTimeout 600ms,setTimeout 600ms, setTimeout 600msclicked===[1,2,3,4] / 태스크 큐: setTimeout 600ms,setTimeout 600ms, setTimeout 600ms / 백그라운드: addEventListener(12)clicked[0].classList.remove("flipped");
clicked[1].classList.remove("flipped");
clicked = [];
를 실행하는데 위의 과정으로 3번과 4번은 remove('flipped')가 적용이 안된 채로 남아있고 clicked 배열을 비우는 것이 문제인걸 확인했다..
카드를 뒷면으로 뒤집고 clicked를 []로 초기화하기 전에 3,4번 카드의 클릭 이벤트 콜백이 실행되는게 문제기 때문에 실행되더라도 아무 일도 하지 않게 만들면 된다.
그렇다면 카드가 2장이 될 때 clickable을 flase로 만들어서 세 번째 카드부터는 클릭해도 아무것도 하지 않고 끝나게 하자
function onCLickCard() {
if (!clickable || completed.includes(this) || clicked[0] === this) {
return;
}
this.classList.toggle("flipped");
clicked.push(this);
if (clicked.length !== 2) {
return;
}
const firstBackColor =
clicked[0].querySelector(".card-back").style.backgroundColor;
const secondBackColor =
clicked[1].querySelector(".card-back").style.backgroundColor;
if (firstBackColor === secondBackColor) {
completed.push(clicked[0]);
completed.push(clicked[1]);
clicked = [];
if (completed.length !== total) {
return;
}
setTimeout(() => {
alert("축하합니다.");
resetGame();
}, 1000);
return;
}
clickable = false; //2장이면 false
setTimeout(() => {
clicked[0].classList.remove("flipped");
clicked[1].classList.remove("flipped");
clicked = [];
clickable = true; //0.6초 뒤에 true로 설정.
}, 600);
}
코드 작성할 때 스코프를 고려해야 하고 짜여진 코드를 분석할 땐 이벤트루프개념을 고려해야겠다는 생각이 들었다. 그리고 실행컨텍스트를 따로 정리하는 시간을 가져야겠다는 생각도 들었다. (반드시 하자!)