자바스크립트 퀴즈
웹을 떠돌거나, 면접 등에서 자바스크립트 코드를 실행하면 콘솔에 어떻게 출력되는지 물어보는 퀴즈를 만날 수 있습니다.
되게 간단하게 생겼지만, 막상 풀어보면 헷갈리거나 틀리는 경우가 많습니다.
(대부분이 일부러 헷갈리게 만들어진 문제라고 생각합니다😡)
현실에서는 콘솔에 여러 번 찍어보면서 확인할 수 있지만, 면접 혹은 코딩 테스트에서는 그럴 수 없죠.
이번 글에서는 자바스크립트 코드를 실행했을 때 콘솔에 출력되는 결과를 맞추는 퀴즈를 풀어보겠습니다.
(문제는 claude-3.5-sonnet 모델의 도움을 받아 만들었습니다🤗)
문제 1
정답 및 해설
- 코드가 차례대로 실행되어
1
이 출력됩니다. console.log('2')
는 당장 실행되지 않고 태스크 큐에 추가됩니다.
현재 태스크 큐:[2출력]
현재 마이크로태스크 큐:[]
console.log('3')
은 당장 실행되지 않고 마이크로태스크 큐에 추가됩니다. 현재 태스크 큐:[2출력]
현재 마이크로태스크 큐:[3출력]
setTimeout(() => console.log('4'), 0)
은 당장 실행되지 않고 마이크로 태스크 큐에 추가됩니다.
현재 태스크 큐:[2출력]
현재 마이크로태스크 큐:[3출력, setTimeout(4출력)]
console.log('5')
는 당장 실행되지 않고 마이크로태스크 큐에 추가됩니다.
현재 태스크 큐:[2출력]
현재 마이크로태스크 큐:[3출력, setTimeout(4출력), 5출력]
console.log('6')
은 당장 실행되지 않고 태스크 큐에 추가됩니다.
현재 태스크 큐:[2출력, 6출력]
현재 마이크로태스크 큐:[3출력, setTimeout(4출력), 5출력]
- 콘솔에
7
이 출력됩니다. - 콜 스택이 비었으니 마이크로태스크 큐에 있는 코드부터 콜 스택으로 이동하여 실행됩니다.
현재 태스크 큐:[2출력, 6출력]
현재 마이크로태스크 큐:[3출력, setTimeout(4출력), 5출력]
- 콘솔에
3
이 출력됩니다. setTimeout(4출력)
이 실행되어4출력
이 태스크 큐에 추가됩니다.
현재 태스크 큐:[2출력, 6출력, 4출력]
현재 마이크로태스크 큐:[5출력]
- 콘솔에
5
가 출력됩니다. - 이제 태스크 큐에 있는 코드가 콜 스택으로 이동하여 실행됩니다.
현재 태스크 큐:[2출력, 6출력, 4출력]
- 콘솔에
2
가 출력됩니다. - 콘솔에
6
이 출력됩니다. - 콘솔에
4
가 출력됩니다.
해설
이 문제는 자바스크립트의 이벤트 루프와 태스크 큐, 마이크로태스크 큐에 대해 이해하고 있는지 묻는 문제입니다.
일단 비동기 코드는 콜 스택이 비워진 후에 실행된다는 것을 알고 있어야 접근할 수 있습니다.
또한, 조금 더 상세하게 마이크로태스크 큐가 태스크 큐보다 우선순위가 높다는 것을 알고 있어야 하며,
setTimeout
과 Promise
가 각각 어디에 속하는지 알고 있어야 풀 수 있습니다.
문제 2
정답 및 해설
- 차례대로 코드가 실행되어
1
이 출력됩니다. setTimeout
의 콜백 함수는 당장 실행되지 않고 태스크 큐에 추가됩니다.- 콘솔에
2
가 출력됩니다. - 콘솔에
4
가 출력됩니다. - 콜 스택이 비었으니 태스크 큐에 있는 코드가 콜 스택으로 이동하여 실행됩니다.
이때 에러가 발생하나,try-catch
블록은 이미 실행 컨텍스트에서 제거되었으므로 에러가 잡히지 않습니다.
해설
문제 1의 심화 버전이라고 할 수 있을 것 같습니다.
비동기 코드가 태스크 큐에 추가되는 것은 문제 1과 동일하지만, 그게 에러라면...?
안타깝게도 try-catch
블록이 이미 실행 컨텍스트에서 제거된 후에 에러가 발생하기 때문에 에러가 잡히지 않습니다.
따라서 console.log('3', error.message)
는 실행되지 않습니다.
문제 3
정답 및 해설
- 반복문 내부의
setTimeout
은 당장 실행되지 않고 태스크 큐에 추가됩니다. - 콘솔에
완료
가 출력됩니다. - 콜 스택이 비었으니 태스크 큐에 있는 코드가 콜 스택으로 이동하여 실행됩니다.
- 콘솔에
3
이 세 번 출력됩니다. - 콘솔에
0
,1
,2
가 출력됩니다.
해설
이 문제는 var
와 let
의 차이를 이해하고 있는지 묻는 문제입니다.
(저는 var
로 선언된 것을 알아채지 못해 틀렸습니다🥲)
var
는 함수 스코프를 가지므로 전역 변수 i
가 선언됩니다.
따라서 세 개의 setTimeout
콜백 함수가 모두 동일한 변수 i
를 참조하게 됩니다.
i
는 반복문이 끝나면 3이 되므로 세 개의 setTimeout
콜백 함수가 모두 3
을 출력합니다.
반면, let
은 블록 스코프를 가지므로 각각 다른 변수 j
를 참조하게 됩니다.
따라서 세 개의 setTimeout
콜백 함수가 각각 0
, 1
, 2
를 출력합니다.
문제 4
정답 및 해설
- 콘솔에
3
이 출력됩니다. asyncFunc
함수가 호출되어 콘솔에1
이 출력됩니다.await
로 인해 현재 실행 중인asyncFunc
의 실행이 일시 중단되고, 자바스크립트 엔진은 다른 작업을 처리할 수 있도록 제어를 반환합니다.
asyncFunc
는Promise
가 해결될 때까지 기다립니다.(마이크로태스크 큐에 추가) 기다리는 동안 원래의 실행 환경으로 돌아갑니다.- 콘솔에
4
가 출력됩니다. asyncFunc
함수가 다시 호출되어 콘솔에1
이 출력됩니다.await
로 인해 현재 실행 중인asyncFunc
의 실행이 일시 중단되고 마이크로태스크 큐에Promise
가 추가됩니다. 자바스크립트 엔진은 다른 작업을 처리할 수 있도록 제어를 반환합니다.- 이제 콜 스택이 비었으니 마이크로태스크 큐에 있는 코드가 콜 스택으로 이동하여 실행됩니다.
- 첫 번째
asyncFunc
의await
가 해결되어await
아래에 있던2
가 출력됩니다. - 두 번째
asyncFunc
의await
가 해결되어await
아래에 있던2
가 출력됩니다. asyncFunc().then(() => console.log('5'));
의then
핸들러가 실행되어 콘솔에5
가 출력됩니다.
해설
이 문제는 async/await
의 동작 방식을 이해하고 있는지 묻는 문제입니다.
async/await
은 비동기 코드를 동기적으로 작성할 수 있게 해주는 문법입니다.
await
키워드는 현재 실행 중인 함수의 실행을 일시 중단하고, 비동기 코드가 해결될 때까지 기다립니다.
여기서 뽀인트는 await
를 만나면 함수의 실행이 일시 중단되고, 다른 작업을 처리할 수 있도록 제어를 반환한다는 점입니다.
문제 5
정답 및 해설
- 콘솔에
1
이 출력됩니다. setTimeout
의 콜백 함수는 당장 실행되지 않고 태스크 큐에 추가됩니다.
현재 태스크 큐:[setTimeout(2출력 후 Promise.then(3출력))]
현재 마이크로태스크 큐:[]
Promise
의then
핸들러는 당장 실행되지 않고 마이크로태스크 큐에 추가됩니다.
현재 태스크 큐:[setTimeout(2출력 후 Promise.then(3출력))]
현재 마이크로태스크 큐:[Promise.then(4출력 후 setTimeout(5출력))]
- 콘솔에
6
이 출력됩니다. Promise
의then
핸들러는 당장 실행되지 않고 마이크로태스크 큐에 추가됩니다.
현재 태스크 큐:[setTimeout(2출력 후 Promise.then(3출력))]
현재 마이크로태스크 큐:[Promise.then(4출력 후 setTimeout(5출력)), Promise.then(7출력)]
- 콜 스택이 비었으니 마이크로태스크 큐에 있는 코드가 콜 스택으로 이동하여 실행됩니다.
- 첫 번째
Promise.then(4출력 후 setTimeout(5출력))
이 실행되어 콘솔에4
가 출력됩니다.setTimeout(5출력)
은 당장 실행되지 않고 태스크 큐에 추가됩니다.
현재 태스크 큐:[setTimeout(2출력 후 Promise.then(3출력)), setTimeout(5출력)]
현재 마이크로태스크 큐:[Promise.then(7출력)]
- 두 번째
Promise.then(7출력)
이 실행되어 콘솔에7
이 출력됩니다. 현재 태스크 큐:[setTimeout(2출력 후 Promise.then(3출력)), setTimeout(5출력)]
현재 마이크로태스크 큐:[]
- 콜 스택이 비었으니 태스크 큐에 있는 코드가 콜 스택으로 이동하여 실행됩니다.
- 먼저
setTimeout(2출력 후 Promise.then(3출력))
의 콜백 함수가 실행되어 우선 콘솔에2
가 출력됩니다. - 그 다음
Promise.then(3출력)
을 마이크로태스크 큐에 추가하고 콜 스택이 비워집니다.
현재 태스크 큐:[setTimeout(5출력)]
현재 마이크로태스크 큐:[Promise.then(3출력)]
- 콜 스택이 비었으니 마이크로태스크 큐에 있는
Promise.then(3출력)
가 콜 스택으로 이동하여 실행됩니다. 콘솔에3
이 출력됩니다.
현재 태스크 큐:[setTimeout(5출력)]
현재 마이크로태스크 큐:[]
- 마지막으로
setTimeout(5출력)
의 콜백 함수가 실행되어 콘솔에5
가 출력됩니다.
해설
이 문제는 좀 더럽네요...
차근차근 코드를 읽으면서 얘는 마이크로태스크 큐에 넣고, 얘는 태스크 큐에 넣고, 콜 스택으로 보내면서 콘솔에 출력되는 순서를 확인해 보면 됩니다.
위의 문제들과 비교했을 때, 새로운 개념이 나오는 문제는 아니고 코드를 읽는 능력을 묻는 문제라고 생각합니다.
조금 헷갈리는 부분은 2
→ 3
→ 5
순서인데, 콜 스택이 비었을 때 작업을 하나씩 콜 스택으로 가져와 실행한다는 점을 알고 있다면 도움이 될 것 같습니다.
2
가 출력되고 Promise.then(3출력)
이 마이크로태스크 큐에 추가되면서 콜 스택의 setTimeout
콜백은 작업을 종료합니다. 즉, 콜 스택이 비워지게 됩니다.
이후 콜 스택이 비었으니 마이크로태스크 큐에 있는 Promise.then(3출력)
이 콜 스택으로 이동하여 실행되어 콘솔에 3
이 출력됩니다.
5
를 출력하는 setTimeout
콜백은 태스크 큐에 추가되어 있으므로 우선순위가 낮아 가장 마지막에 출력됩니다.
결론
어떠셨나요? 전부 맞추셨나요?
자바스크립트로 이런 퀴즈나 면접 질문을 만들면 거의 다 실행 컨텍스트, 이벤트 루프 위주인 것 같습니다.
아무래도 핵심 개념이기도 하고, 헷갈리기 딱 좋은 부분이라 그런 것 아닐까요?
해설을 적으면서 큐를 표현하는 게 조금 어려워서 알아보기 쉬운 방식으로 적는 것에 초점을 맞춰 보았습니다.
실제로는 Promise
자체는 즉시 생성 및 실행되지만, Promise
의 결과를 처리하는 콜백 함수들이 마이크로태스크 큐에 추가되어 나중에 실행됩니다.
착오 없으시길 바랍니다...🤗