script 태그의 위치

렌더링 엔진은 script태그를 다운로드하거나 실행하는 중에는 파싱을 멈춘다.
자바스크립트 파일은 css나 html을 변경시킬 가능성이 있기 때문이다.

  1. head,body태그 최상단

파싱을 시작하자마자 script 태그를 다운로드한다.
파싱을 막기 때문에 script태그를 먼저 다운로드하고 실행 후 파싱한다.
실행 순서를 정할 수 있다.
다만 존재하지 않는 DOM요소에 접근시 에러가 발생할 수 있다.

  1. body태그 최하단

파싱이 끝난 뒤에 script 태그를 다운로드 시작한다.
js파일에 의존성이 높을경우 사용자경험이 좋지 못하다.

  1. defer

병렬로 백그라운드에서 다운로드한다.
다운로드 하는 중에도 HTML 파싱을 멈추지 않는다.
defer 스크립트의 실행은 페이지 구성이 끝날 때까지 지연된다.

defer 스크립트는 DOM이 준비된 후에 실행되지만 DOMContentLoaded 전에 실행된다.

아래는 예시 코드이다.

1
2
3
4
5
6
7
8
9
<p>...스크립트 앞 콘텐츠...</p>

<script>
document.addEventListener('DOMContentLoaded', () => alert("`defer` 스크립트가 실행된 후, DOM이 준비되었습니다!")); // (2)
</script>

<script defer src="https://javascript.info/article/script-async-defer/long.js?speed=1"></script>

<p>...스크립트 뒤 콘텐츠...</p>
  1. 페이지의 콘텐츠가 모두 출력된다.

  2. DOMContentLoaded 이벤트는 defer 스크립트가 실행된 후에 발생하므로 alert창은 DOM트리가 완성되고 defer 스크립트의 실행이 완료된 후에 실행된다.

  3. async

async도 마찬가지로 백그라운드에서 다운로드 된다. 따라서 HTML 페이지는 async 스크립트 다운이 완료되길 기다리지 않고 페이지 내 컨텐츠를 출력한다.( 하지만 async 스크립트 실행중에는 HTML 파싱이 멈춘다.)

DOMContentLoaded 이벤트와 async 스크립트는 서로를 기다리지 않는다.

  • DOM 트리를 다 만든 후에 async 스크립트 다운로드가 끝났을 경우 DOMContentLoaded 이벤트는 async 스크립트 실행 전에 발생할 수 있다.
  • 마찬가지로 async 스크립트가 DOM트리를 만들기 전에 다운로드 되었을 경우 async 스크립트의 실행이 끝난 다음 DOMContentLoaded 이벤트가 발생한다.

아래는 예제 코드이다.

1
2
3
4
5
6
7
8
9
10
<p>...스크립트 앞 콘텐츠...</p>

<script>
document.addEventListener('DOMContentLoaded', () => alert("DOM이 준비 되었습니다!"));
</script>

<script async src="https://javascript.info/article/script-async-defer/long.js"></script>
<script async src="https://javascript.info/article/script-async-defer/small.js"></script>

<p>...스크립트 뒤 콘텐츠...</p>

async 스크립트는 그 특징 떄문에 실행순서를 보장할 수 없다. 먼저 다운로드가 끝난 스크립트 순으로 실행된다. 즉 small.js가 long.js보다 먼저 실행된다.

또한 alert의 실행시점도 예측할 수 없다.

이러한 특징으로 인해 구글 어널리틱스같은 독립적인 역할을 하는 서드파티 스크립트를 삽입할 때에 유용하다. 개발중인 스크립트에 의존하지 않고 독립적으로 동작하기 때문이다.

1
<script async src="https://google-analytics.com/analytics.js"></script>

Ref

defer, async 스크립트

댓글