HTML <script> 태그의 async와 defer, 확실히 알고 쓰시나요?

HTML <script> 태그의 async와 defer 속성 이해하기

모든 웹 개발자는 자신이 HTML에 능숙하다고 자랑스럽게 말합니다.
 

하지만… 정말로 <script> 태그의 asyncdefer 속성 차이를 알고 계신가요.

 
이 속성들은 현대 프론트엔드(frontend) 웹 애플리케이션(application)에서 흔히 사용되지만, 종종 간과되곤 합니다.
 
먼저 요약해 보겠습니다.
 

HTML에서 <script> 태그는 세 가지 방식으로 사용될 수 있으며, 아래에서 하나씩 설명하겠습니다.

  • 기본 방식 (순차적 실행): <script src='https://...'></script>
  • 다운로드 후 즉시 실행: <script src='https://...' async></script>
  • 문서 파싱 완료 후 실행: <script src='https://...' defer></script>

기본 태그

설명

브라우저(browser)가 HTML 문서를 파싱(parsing)하다가 기본 <script> 태그를 만나면 다음과 같이 동작합니다.

  1. 나머지 HTML 문서의 파싱(parsing)을 중단합니다.
  1. 자바스크립트(JavaScript) 스크립트(script)를 다운로드하고, 스크립트(script) 다운로드가 완료되면 즉시 실행합니다.
  1. 스크립트(script) 실행이 완료된 후, 중단되었던 HTML 파싱(parsing)을 재개합니다.

다음 예제를 살펴보겠습니다.

<html lang="ko">
  <head>
    <script>
      console.log('첫 번째 스크립트');
    </script>
    <script src="https://.../Chart.min.js"></script>
    <script src="https://.../moment.min.js"></script>
    <script src="https://.../vue.min.js"></script>
  </head>
  <body>
    페이지 콘텐츠 1
  </body>
</html>

실행 순서

  1. console.log("첫 번째 스크립트"); 로그가 출력됩니다.
  1. 외부 스크립트(script) Chart.min.js를 다운로드하고 실행합니다.
  2. (이 동안 HTML 파싱 중단)
  3. moment.min.js를 다운로드하고 실행합니다.
  4. (이 동안 HTML 파싱 중단)
  5. vue.min.js를 다운로드하고 실행합니다.
  6. (이 동안 HTML 파싱 중단)
  7. 텍스트 콘텐츠 "페이지 콘텐츠 1"이 화면에 표시됩니다.
  8. (모든 스크립트 실행 후 HTML 파싱 재개)

주의사항

기본 <script> 태그는 스크립트(script)를 HTML 문서에 나타나는 순서대로 실행합니다.

 
만약 특정 스크립트(script) 실행 시간이 너무 오래 걸리면, 후속 스크립트(script)들은 대기 상태가 되고 HTML 파싱(parsing)도 계속 중단됩니다.
 
이로 인해 사용자는 스크립트(script) 실행이 완료될 때까지 빈 화면(white screen)을 보게 될 수 있습니다.

태그

설명

브라우저(browser)가 async 속성이 있는 <script> 태그를 만나면 다음과 같이 동작합니다.

  1. HTML 파싱(parsing)을 중단하지 않고, 자바스크립트(JavaScript) 스크립트(script)를 비동기적으로(asynchronously) 다운로드합니다.
  2. (다운로드와 HTML 파싱이 병렬로 진행됨)
  3. 스크립트(script) 다운로드가 완료되면, 즉시 실행합니다.
  4. 이때는 HTML 파싱(parsing)이 일시 중단됩니다.
  1. 스크립트(script) 실행이 완료되면, 중단되었던 HTML 파싱(parsing)을 재개합니다.

다음 예제를 살펴보겠습니다.

<html lang="ko">
  <head>
    <script>
      console.log('첫 번째 스크립트');
    </script>
    <script async src="https://.../Chart.min.js"></script>
    <script async src="https://.../moment.min.js"></script>
    <script async src="https://.../vue.min.js"></script>
  </head>
  <body>
    페이지 콘텐츠 2
  </body>
</html>

실행 순서

  1. console.log("첫 번째 스크립트"); 로그가 출력됩니다.
  1. Chart.min.js, moment.min.js, vue.min.js를 비동기적으로 다운로드 시작합니다.
  2. 동시에 HTML 파싱(parsing)은 계속 진행됩니다.
  1. 세 개의 스크립트(script) 중 다운로드가 먼저 완료된 스크립트(script)부터 즉시 실행됩니다.(어떤 스크립트가 먼저 실행될지는 예측할 수 없습니다).
  2. 스크립트(script) 실행 중에는 HTML 파싱(parsing)이 일시 중단됩니다.
  1. 스크립트(script) 실행이 끝나면 HTML 파싱(parsing)이 재개됩니다.(스크립트 실행 시점과 무관하게 표시될 수 있음)
  2. "페이지 콘텐츠 2"는 HTML 파싱(parsing)이 완료되는 시점에 화면에 표시됩니다.

주의사항

async 속성이 있는 스크립트(script)는 다운로드 중에는 HTML 파싱(parsing)을 중단시키지 않지만, 실행 중에는 중단시킵니다.

 
만약 HTML 구조가 단순하여 스크립트(script)가 실행되기 전에 페이지 로딩이 완료된다면, "페이지 콘텐츠 2"가 먼저 보이고 그 후에 스크립트(script)들이 실행될 수 있습니다.
 
반대로 HTML 구조가 복잡하여 파싱(parsing)에 시간이 오래 걸린다면, 스크립트(script)가 다운로드 완료되어 실행될 때 파싱(parsing) 과정이 중단될 수 있습니다.
 

또한, 여러 async 스크립트(script)들은 다운로드 완료 순서대로 실행되므로, HTML 문서에 작성된 순서대로 실행된다는 보장이 없습니다.

태그

설명

브라우저(browser)가 defer 속성이 있는 <script> 태그를 만나면 다음과 같이 동작합니다.

  1. HTML 파싱(parsing)을 중단하지 않고, 자바스크립트(JavaScript) 스크립트(script)를 비동기적으로(asynchronously) 다운로드합니다.
  2. (다운로드와 HTML 파싱이 병렬로 진행됨)
  3. 스크립트(script) 다운로드가 완료되더라도 즉시 실행하지 않습니다.
  1. 전체 HTML 문서의 파싱(parsing)이 완전히 완료된 후에 스크립트(script)를 실행합니다.
  2. (정확히는 DOMContentLoaded 이벤트 발생 직전에 실행됩니다).

다음 예제를 살펴보겠습니다.

<html lang="ko">
  <head>
    <script>
      console.log('첫 번째 스크립트');
    </script>
    <script defer src="https://.../Chart.min.js"></script>
    <script defer src="https://.../moment.min.js"></script>
    <script defer src="https://.../vue.min.js"></script>
  </head>
  <body>
    페이지 콘텐츠 3
  </body>
</html>

실행 순서

  1. console.log("첫 번째 스크립트"); 로그가 출력됩니다.
  1. Chart.min.js, moment.min.js, vue.min.js를 비동기적으로 다운로드 시작합니다.
  2. 동시에 HTML 파싱(parsing)은 계속 진행됩니다.
  1. 스크립트(script)들은 다운로드가 완료되어도 즉시 실행되지 않고 대기합니다.
  2. HTML 파싱(parsing)이 완료되고 "페이지 콘텐츠 3"이 화면에 표시될 때까지 기다립니다.
  1. HTML 문서 파싱(parsing)이 모두 완료된 후, 다운로드된 스크립트(script)들을 HTML 문서에 작성된 순서대로 실행합니다.
  2. (Chart.min.js -> moment.min.js -> vue.min.js 순서).

주의사항

만약 여러 개의 defer 스크립트(script)가 있다면, 브라우저(browser)는 이들을 병렬로 다운로드합니다.

 
하지만 다운로드 속도와 관계없이, 실행 순서는 항상 HTML 문서에 명시된 순서를 따릅니다.
 
이는 스크립트(script) 간의 의존성이 있을 때 유용합니다.

혼합 사용

실제 개발 환경에서는 여러 방식을 혼합하여 사용할 수 있습니다.
 
다음 예제를 살펴보겠습니다.

<html lang="ko">
  <head>
    <script>
      console.log('첫 번째 스크립트');
    </script>
    <script defer src="https://.../Chart.min.js"></script>
    <script async src="https://.../moment.min.js"></script>
    <script defer src="https://.../vue.min.js"></script>
  </head>
  <body>
    페이지 콘텐츠 4
  </body>
</html>

실행 순서

  1. console.log("첫 번째 스크립트"); 로그가 출력됩니다.
  1. Chart.min.js(defer), moment.min.js(async), vue.min.js(defer)를 비동기적으로 다운로드 시작합니다.
  1. 이 HTML 예제는 구조가 단순하므로, 아마도 스크립트(script) 실행 전에 HTML 파싱(parsing)이 완료되어 "페이지 콘텐츠 4"가 먼저 화면에 표시될 것입니다.
  1. moment.min.js(async)는 다운로드가 완료되는 즉시 실행됩니다.
  2. (이때 HTML 파싱이 아직 진행 중이었다면 잠시 중단될 수 있습니다).
  1. HTML 파싱(parsing)이 완료된 후, Chart.min.js(defer)와 vue.min.js(defer)가 HTML 문서 순서대로 실행됩니다.

요약

  • 기본 <script>: HTML 파싱(parsing)을 차단하고, 스크립트(script)를 순서대로 다운로드 및 실행합니다.
  • <script defer>: HTML 파싱(parsing)을 차단하지 않고 병렬로 다운로드하며, HTML 파싱 완료 후 스크립트(script)를 문서 순서대로 실행합니다.
  • <script async>: HTML 파싱(parsing)을 차단하지 않고 병렬로 다운로드하며, 다운로드가 완료되는 즉시 실행합니다.
  • 실행 시에는 HTML 파싱(parsing)을 차단할 수 있으며, 실행 순서는 보장되지 않습니다.

모범 사례 (Best Practices)

  • 스크립트(script)가 독립적이며 다른 스크립트(script)나 DOM에 의존하지 않는 경우, async가 좋은 선택일 수 있습니다.
  • (하지만 실행 시 파싱(parsing)을 잠시 중단시킬 수 있다는 점을 유의해야 합니다).
  • 스크립트(script)들이 서로 의존하거나 DOM 요소에 접근해야 하는 경우, defer를 사용하여 실행 순서를 보장하고 HTML 파싱(parsing) 완료 후 실행되도록 하는 것이 좋습니다.
  • 만약 asyncdefer 속성이 모두 존재한다면, 대부분의 최신 브라우저(browser)에서는 async가 우선적으로 적용됩니다.
  • (하지만 명확성을 위해 둘 중 하나만 사용하는 것이 좋습니다).