본문으로 바로가기

[JavaScript] JavaScript 기본 동작

JavaScript가 웹에서 동작하는 아주 기본적인 개념에 대해 간단하게 알아보겠습니다. JavaScript의 특징은 single thread 싱글 스레드라는 것입니다. 즉 1번에 1개의 동작 밖에 하지 못 합니다. 그런데 JavaScript 개발자라면 누구나 비동기 작업을 해본 경험이 있을 것입니다. Single thread? 비동기? 뭔가 같이 있으면 안될 것 같은 단어 2개가 JavaScript 특징으로 많이 소개 되고 있습니다. 필자는 JavaScript는 비동기로 동작한다는 사실을 알고 궁금한 내용이 있었습니다. 어떨 때 비동기로 동작하고 어떨 땐 동기로 동작할까?

console.log("hi 1")
console.log("hi 2")
console.log("hi 3")
console.log("hi 4")
console.log("hi 5")

위 코드의 결과가

hi 1

hi 2

hi 3

hi 4

hi 5

가 아니라

hi 2

hi 1

hi 3

hi 5

hi 4

이렇게 막 뒤죽박죽 섞일 수도 있을까? 등 여러 궁금증이 있었습니다. 그리고 JavaScript는 timer 동작을 정확하게 못 한다고 합니다. 이러한 내용들이 왜 이렇게 동작할 수 밖에 없는 지 이유에 대해 JavaScript 동작 방식(순서)에 대해 알아보며 이해해보겠습니다.

이번 게시글을 통하여 아래 개념들을 이해할 수 있습니다.

  • - JavaScript에서 timer (setTimeout ...)는 왜 정확한 시간에 실행하지 못 할까?

  • - Event Loop (이벤트 루프)

  • - Rendering (렌더링)

  • - stack에 오래 걸리는 작업을 쌓아두면 안되는 이유

  • - ...etc

JavaScript 기본 동작

JavaScript는 single thread입니다. 아래 이미지를 참고해봅시다.

위 이미지를 참고하여 아래 코드가 어떻게 실행될지 알아봅시다.

function foo() {
  console.log("hi");
}
foo();

위 코드가 실행되는 경우 stack의 변화는 아래와 같습니다.

처음에 stack이 비어 있다가 main 함수가 실행되고 foo 함수 console.log 함수 순서로 실행되며 나중에는 하나씩 pop 되어 사라집니다. 이렇게 stack은 동작합니다. 그럼 아래 코드는 어떻게 될까요?

console.log("hi 1");

setTimeout(()=>{
  console.log("hi 3");
}, 1000)

console.log("hi 2");

어떻게 위 코드가 동작할 지 상상해봅시다. 위 코드는 아래와 같이 동작합니다.

1 2
3 4
5 6
7 8

이해가 되시나요? stack에서는 계속 남아 있는 작업을 처리하고 있습니다. 그러다 중간에 setTimeout 함수가 들어오게 되는 데 이 함수의 경우 web api이므로 stack에서 처리하는 것이 아닌 web api에서 기다리게 됩니다. 그 동안 stack은 자신이 하고 있는 일을 계속 하는 것이죠. 여기까지는 이해가 쉽게 되셨나요? 그 이후 setTimeout이 1초가 지난 경우 stack에 바로 들어가는 것이 아닌 task queue에 들어가게 됩니다. stack이 하고 있는 일을 기다리고 있는 것이죠. 이후 stack이 다 비워졌으면 event loop가 task queue에 있는 setTimeout callback 함수를 stack으로 가져와 실행합니다. 간단하게 정리하면 아래와 같습니다.

  • - main 함수 stack에서 실행

  • - setTimeout web api에서 대기

  • - 1초 후 task queue에서 callback 함수 대기

  • - event loop가 stack 확인 후 다 비워진 경우 task queue에 있는 cb을 stack으로 이동

  • - stack에서 callback함수 실행

위와 같이 동작을 합니다. 그럼 여기서 JavaScript timer가 왜 정확한 시간을 지키지 못 하는 지 이해할 수 있습니다. 위 동작 방식으로 인해 stack에서 하고 있는 작업이 10분이 걸리는 작업인 경우 setTimeout으로 1초를 걸어도 10분 뒤에 실행이 됩니다. 이해가 되시나요? 그럼 아래 코드는 어떨까요?

console.log("hi 1");

setTimeout(()=>{
  console.log("hi 3");
}, 0)

console.log("hi 2");

이번에는 setTimeout을 0초 걸었습니다. 그럼 결과는 아래와 같을까요?

hi 1

hi 3

hi 2

아닙니다.

hi 1

hi 2

hi 3

이번에도 위와 같은 결과가 나옵니다. 이것이 제대로 이해가 안되시면 위 2번째 예시를 다시 확인해보세요. setTimeout은 stack에서 처리하는 것이 아니라 web api에서 처리하기 때문에 0초 뒤 실행을 하여도 stack에 쌓여있는 코드보다 더 늦게 실행이 되는 것입니다.

Rendering (렌더링)

그럼 화면을 그리는 rendering 작업은 언제할까요? 다행히 render의 경우 callback 함수의 실행보다 높은 우선 순위를 가지고 있습니다. 따라서 stack이 비워진 경우 event loop가 task queue에 있는 callback을 가져오기 전 render가 일어나게 됩니다. 이 말은 무엇이냐? 다시 말하면 stack에는 오래 걸리는 작업을 두면 안된다는 것입니다. 만약 stack에 10분이 소요되는 작업이 있다고 하면 10분 동안은 rendering이 일어나지 않을 것입니다. 따라서 때로는 동기적인 작업을 비동기로 만들어야 할 경우가 있습니다. 아래 코드를 확인해봅시다.

[1,2,3,4].forEach(function() {
  // 오래 걸리는 작업
  longWork();
});

위 동기적 코드의 경우 stack은 오래 걸리는 4개의 작업에 점령당해 다른 어떠한 작업도 하지 못 할 것입니다. 즉 rendering 조차 하지 못 한다는 것입니다. 그러나 아래 비동기로 변경한 코드를 확인해봅시다.

function asyncForEach(array, callback) {
  array.forEach(function() {
    setTimeout(callback, 0);
  })
}

asyncForEach([1,2,3,4], function() {
  // 오래 걸리는 작업
  longWork();
})

위 코드의 경우 setTimeout을 통해 web api에서 기다리게 됩니다. 이후 task queue에 들어가게 되죠. 그러나 이 경우 해당 작업이 stack에 들어가기 전 stack이 비어진 경우 rendering을 할 수 있도록 여유를 줍니다. 즉 화면 rendering을 할 수 있게 되죠. 이 경우가 아니라 동기적인 경우에는 사용자는 해당 작업이 끝날 때까지 아무 것도 못 하게 되지만 이 경우는 여유를 줄 수 있습니다. 즉, 여기서 말하자고 하는 것은 stack에 오래 걸리는 작업을 넣어두어 사용자에게 불편함을 주지 말자라는 것입니다.

쉽지 않은 내용들이었습니다. 이해가 안되시면 댓글 남겨주셔도 됩니다. 그 전 참고자료에 있는 youtube 영상을 2-3번 시청하시는 것을 매우 추천 드립니다.

참고자료

마지막

해당 내용은 틀릴 수도 있다는 것을 감안하여 봐주세요. 틀린 내용 및 오탈자 수정 요청 환영입니다.

반응형