<script>로 HTML 문서에 자바스크립트 넣기 (feat. defer & async)
Dec 30, 2020 · 6 min read



웹 개발을 하다보면 <script> 엘리먼트를 이용해서 HTML 문서 안에 자바스크립트 코드를 삽입하는 경우가 필연적으로 생기기 마련인데요. 이번 포스트에서는 이렇게 정말 자주 사용하게 되는 <script> 엘리먼트를 좀 더 효과적으로 사용하는 방법에 대해서 알아보겠습니다.

script 엘리먼트의 위치

혹시, 브라우저가 <script> 엘리먼트를 어떻게 처리하는지 생각해보신 적이 있으신가요? 당연한 얘기처럼 들리기시겠지만 브라우저는 HTML 문서를 처리하다가 <script> 엘리먼트를 만나면 src 속성에 명시된 경로의 파일을 내려받아 자바스크립트 코드를 실행합니다.

여기서 <script> 엘리먼트가 HTML 문서에서 어디에 위치하는지가 웹 페이지의 로딩 속도에 영향을 미칠 수가 있는데요.

아래와 같이 <script> 엘리먼트가 <body> 엘리먼트의 중간에 오게되면 어떤 일이 일어날까요?

<body>
  <h2>A</h2>
  <script src="./script.js"></script>
  <h2>B</h2>
</body>

위 HTML 문서는 브라우저에서 다음과 같이 순차적으로 처리가 됩니다.

  • <h2>A</h2>가 화면에 출력됨
  • script.js 파일을 내려받아 자바스립트 코드가 실행됨
  • <h2>B</h2>가 화면에 출력

브라우저로 위 HTML 문서를 직접 열어보면 처리 속도가 워낙 빨라서 이 부분을 눈으로 확인하는 것은 불가능하겠지요? 다음과 같이 일부로 3초의 지연 시간이 발생하도록 script.js 파일 안에 자바스크립트 코드를 작성합니다.

// script.js
function sleep(ms) {
  const wakeUpTime = Date.now() + ms
  while (Date.now() < wakeUpTime) {}
}

sleep(3000)

이제 이 HTML 문서를 브라우저로 열어보면 A가 화면에 먼저 나오고, 약 3초 후에 B가 화면에 뜨는 것을 보실 수 있으실 겁니다.

이렇게 <script> 엘리먼트 때문에 브라우저에서 발생하는 HTML 코드 처리 중 딜레이는 매우 단순한 방법으로 최소화 할 수 있습니다. 바로 <script> 엘리먼트를 <body> 엘리먼트의 가장 마지막으로 옮겨주는 것입니다.

<body>
  <h2>A</h2>
  <h2>B</h2>
  <script src="./script.js"></script>
</body>

다시 브라우저에서 이 HTML 문서를 열어보면 화면에 AB가 동시에 출력되는 것을 보실 수 있으실 겁니다.

정반대로 <script> 엘리먼트를 <head> 엘리먼트 안으로 이동시키면 어떤 일이 일어날까요?

<head>
  <script src="./script.js"></script>
</head>
<body>
  <h2>A</h2>
  <h2>B</h2>
</body>

이 HTML 문서를 브라우저로 열어보면, 브라우저에 AB가 3초 후에 동시에 나타날 것입니다.

전통적으로 HTML 입문자들은 <script> 엘리먼트를 <head> 엘리먼트 안에 넣도록 배우는 경우가 많았습니다. 하지만 실제 프로젝트에서 개발을 하다보면 <body> 엘리먼트의 제일 마지막에서 <script> 엘리먼트를 보게되는 경우가 훨씬 많다는 것을 사용자에게 보다 최적화된 웹페이지 로딩 경험을 제공하려면 <script> 엘리먼트를 <body> 엘리먼트의 마지막에 넣는 것이 유리하기 때문입니다.

defer 속성 활용

위에서 살펴본 것 처럼 <script> 엘리먼트를 <body> 엘리멘트의 마지막에 위치시킴으로써 브라우저가 막힘없이 HTML 코드 처리할 수 있도록 도울 수 있습니다. 하지만 내려받아야 하는 자바스크립트 파일의 크기가 크거나 네트워크 속도가 느린 경우, 단순히 <script> 엘리먼트를 위치를 조정해주는 것만으로는 부족할 수가 있습니다.

왜냐하면, 화면에 모든 HTML 코드가 처리되어 출력이 완료된 상태에서 자바스크립트 파일아 내려받아지는 동안 브라우저에서는 행이 걸린 것처럼 느껴질 수 있기 때문입니다. 이 상태에서 사용자는 웹사이트의 출력 결과를 브라우저 상에서 볼 수는 있지만 웹사이트와 상호 작용은 아직 할 수가 없는데요.

<script> 엘리먼트에 defer 속성을 활용하면, 이렇게 브라우저에서 자바스크립트 파일을 내려받는 시간을 취소화할 수 있습니다. 왜냐하면 defer 속성을 사용하면 브라우저는 HTML 코드가 처리하면서 동시에 자바스크립트 파일도 내려받기 때문입니다. 뿐만 아니라, 해당 <script> 엘리먼트가 HTML 문서 내의 어디에 위치하더라도, 마치 <body> 엘리먼트의 제일 마지막에 넣은 것처럼 HTML 코드가 모두 처리된 이후에 자바스크립트 코드가 실행이 됩니다.

예를 들어, 브라우저가 HTML 코드를 처리하는데 0.3초가 걸리고, 자바스크립트 파일을 내려 받는데 0.2초가 걸린다면, 내려받은 자바스크립트 코드는 0.3초 후, 즉 HTML 코드가 처리되지 마자 바로 실행될 수 있습니다. 왜냐하면 HTML 코드를 처리하는 동안 해당 자바스크립트 파일의 다운로드는 이미 끝났기 때문입니다.

하지만 defer 속성을 사용하지 않는다면, 브라우저가 HTML 코드를 처리하는데 0.3초, 자바스크립트 파일을 내려 받는데 0.2초가 순차적으로 소요되어, 내려받은 자바스크립트 코드는 0.5초 후에 실행되게 됩니다.

<body>
  <h2>A</h2>
  <script defer src="./script.js"></script>
  <h2>B</h2>
</body>

위 HTML 문서를 브라우저에서 열어보면, 마치 <script> 엘리먼트를 <body> 엘리먼트의 제일 마지막에 넣은 것처럼 브라우저 화면에 AB가 동시에 출력되는 것을 보실 수 있으실 겁니다.

async 속성 활용

<script> 엘리먼트는 defer 속성과 유사한 async라는 속성도 HTML5 부터 제공하고 있는데요. 이 두 개의 속성은 동작 방식에서 미묘한 차이가 있지만 무분별하게 구분없이 사용되는 경우를 자주 보게 됩니다.

async 속성을 사용하면 브라우저는 HTML 코드를 처리하는 동안 자바스크립트 파일이 내려받는 것뿐만 아니라, 다운로드가 끝나자마자 지체없이 내려받은 자바스크립트 코드를 실행합니다. 따라서 개발자는 해당 자바스크립트 코드가 어느 시점에 내려받아져 실행이 될지 알 수 없습니다. 자바스크립트 코드가 작은 경우, HTML 코드의 처리보다 먼저 끝날 수도 있고, 자바스크립트의 코드가 큰 경우, HTML 코드의 처리보다 늦게 끝날 수도 있습니다.

<body>
  <h2>A</h2>
  <script async src="./script.js"></script>
  <h2>B</h2>
</body>

위 HTML 문서를 브라우저에서 열어보면, defer 속성을 사용했을 때와 마찬가지로 마치 브라우저 화면에 AB가 동시에 출력되는 것을 보실 수 있으실 겁니다.

defer vs. async

자, 그럼 결론적으로 언제 defer 속성을 사용하는 게 낫고, 언제 async 속성을 사용하는 게 나을까요? 자바스크립트 파일의 다운로드 측면에서는 defer 속성과 async 속성은 큰 차이가 없습니다. 두 가지 방식에서 모두 브라우저는 HTML 코드를 실행하면서 백그라운드에서 자바스크립트 파일을 다운로드 하기 때문입니다.

하지만 자바스크립트 코드의 실행 측면에서 았을 때는 defer 속성과 async 속성 간에는 의미있는 차이가 생깁니다. 서로 연관이 있는 여러 개의 스크립트 파일을 순차적으로 실행해야하는 경우에는 defer 속성을 사용하는 편이 안전합니다. 왜냐하면 defer 속성은 자바스크립트 코드를 HTML 문서에 나타는 순서대로 실행을 해주기 때문입니다.

<!-- 병렬 로드, 순차 실행 -->
<script defer src="./script-1.js"></script>
<script defer src="./script-2.js"></script>
<script defer src="./script-3.js"></script>

반면에 여러 개의 독립된 자바스크립트 파일을 삽입하는 경우에는 (ex. 광고/통계용 외부 스크립트) async 속성을 사용하는 편이 성능 측면에서 유리합니다. 왜냐하면 async 속성을 사용해서 삽입된 자바스크립트 코드는 HTML 문서에 나타나는 순서대로 실행이된다는 보장이 없고 다운로드 완료 즉시 실행되기 때문입니다. 하나의 자바스크립트 파일이 동일 HTML 문서 내에 삽입된 다른 자바스크립트 파일에 의존하는 경우 예상치 못한 자바스크립트 오류가 발생할 위험이 있습니다.

<!-- 병렬 로드, 병렬 실행 -->
<script async src="./google-ad.js"></script>
<script async src="./facebook-ad.js"></script>
<script async src="./naver-ad.js"></script>

마치면서

이상으로 <script> 엘리먼트를 HTML 문서 상에서 어디에 위치시키는지에 따라 웹 페이지의 로딩 속도에 어떤 영향을 미칠 수가 있는지 알아보았습니다. 더불어, 자바스크립트 파일의 다운로드와 자바스크립트 코드의 실행을 최적화하는데 도움이 될 수 있는 <script> 엘리먼트의 defer 속성과 async 속성에 대해서도 살펴보았습니다. <script> 엘리먼트에 대한 좀 더 상세한 내용은 MDN 공식 문서 - script: The Script element를 참고하시길 바랍니다.






Engineering Blog  by Dale Seo