데브로그

프로그래밍 팁 그리고 프로그램 사용 방법 등을 공유 합니다.

JavaScript

JavaScript 파일 사용할 때 script 태그 처리 방법 (async vs defer)

웹사이트 코드 작성시 <script> 태그는 <head> 태그나 <body> 태그 아무 곳에서나 사용할 수 있습니다. 웹사이트 로드 시에 HTML을 파싱하다가 <script> 코드를 만나면 HTML 을 파싱하는 것을 멈추고 JavaScript를 실행하기 때문에 HTML이 제대로 렌더링이 안되거나 웹사이트 퍼포먼스에 영향을 끼칩니다.

.

우선 <script> 태그가 HTML코드와 직접적인 연관성이 없는 경우 <head> 태그나 <body> 태그 아무 곳에서나 사용할 수 있습니다.

예를 들어 console창에 “hello world”를 찍어주는 hello.js 파일을 가져다 사용하는 경우를 생각해 보겠습니다.

.

// hello.js
console.log('Hello world')

.

hello.js를 <head> 태그 안에 사용했습니다.

.

<html>
  <head>
    <title>Hello World</title>
    <script src="src/js/hello.js"></script>
  </head>
  <body>
    <p>Hello World</p>
  </body>
</html>

.

Console 창에서 아무런 오류 없이 Hello World를 출력하는 것을 확인할 수 있습니다.

.

이번에는 <body> 태그 안에 사용했습니다.

.

<html>
  <head>
    <title>Hello World</title>
  </head>
  <body>
    <p>Hello World</p>
    <script src="src/js/hello.js"></script>
  </body>
</html>

.

이번에도 Console 창에서 아무런 오류 없이 Hello World를 출력하는 것을 확인할 수 있습니다.

.

hello.js 파일의 console.log 함수는 HTML 요소와 관련되지 않은 코드로 독립적으로 실행되기에 <head> 태그에 있던 <body> 태그에 있던 이상이 없습니다.

그러나 JavaScript 코드가 <script> 태그 다음에 실행되는 HTML 요소와 관련되었을 때는 문제가 있습니다.

.

<p> 태그 다음에 또 다른 <p> 태그를 추가하는 코드를 사용하는 경우를 예로 들어보겠습니다.

.

<html>
  <head>
    <title>Document</title>
  </head>
  <body>
    <div id="box">
      <p id="first">Hello World</p>
    </div>
    <script>
      const box = document.querySelector("#box");
      const second = document.createElement("p");
      second.textContent = "Hello World 2";
      box.appendChild(second);
    </script>
  </body>
</html>

.

우선 <body> 태그의 클로징 태그 </body> 바로 앞에 <script> 태그를 사용했을 경우에는 오류가 발생하지 않습니다.

.

<html>
  <head>
    <title>Document</title>
    <script>
      const box = document.querySelector("#box");
      const second = document.createElement("p");
      second.textContent = "Hello World 2";
      box.appendChild(second);
    </script>
  </head>
  <body>
    <div id="box">
      <p id="first">Hello World</p>
    </div>
  </body>
</html>

.

<head> 태그에 사용했을 때 다음과 같은 에러를 발생합니다.

‘Uncaught TypeError: Cannot read properties of null (reading ‘appendChild’)’

그 이유는 <script> 태그가 실행되는 시점에 <div> 태그가 아직 렌더링 되지 않아서 존재하지 않기 때문입니다.

.

이러한 오류를 해결하기 위해서 JavaScript 코드에 HTML이 완전히 로드가 되면 JavaScript 코드를 실행할 수 있도록 다음과 같이 코드를 작성합니다.

.

<html>
  <head>
    <title>Document</title>
    <script>
      document.addEventListener("DOMContentLoaded", function () {
        const box = document.querySelector("#box");
        const second = document.createElement("p");
        second.textContent = "Hello World 2";
        box.appendChild(second);
      });
    </script>
  </head>
  <body>
    <div id="box">
      <p id="first">Hello World</p>
    </div>
  </body>
</html>

.

위에는 <script> 태그 안에 Javascript 코드를 사용한 예 입니다.

이번에는 jQeury CDN을 가져와서 사용해 보는 것을 예를 들어보겠습니다. 현재 jQuery는 3.7.0 기준 unminified 파일 사이즈가 279kb이고 minified 파일 사이즈가 86kb 입니다. 실제로 웹사이트 로딩시 279kb 는 기본적으로 다루는 파일 사이즈 보다 상대적으로 매우 큰 사이즈입니다. 거의 대부분의 경우 jQuery 라이브러리 소스코드를 직접 수정할 필요는 없어서 minified 버전을 사용하기 때문에 unminified 파일은 사용하지 않습니다. 하지만 이와 비슷한 사이즈의 JavaScript 라이브러리를 가져와서 사용하고자 할 때를 가정해 보면 <head> 태그 안에 <script> 태그를 그대로 사용하는 것은 웹사이트 퍼포먼스에 안좋습니다.

.

다음은 <script src=”jslibrary.js”>를 사용했을 때의 웹 페이지가 렌더링 되는 순서입니다.

.

.

이와 같이 작성하면, script의 수 만큼 웹페이지 로딩 속도가 늘어나게 됩니다.

.

async

다음은 <script src=”jslibrary.js” async>를 사용했을 때의 웹 페이지가 렌더링 되는 순서입니다.

.

.

HTML이 파싱 되면서 <script> 코드를 만났을 때 HTML 파싱이 멈추지 않고 계속되면서 <script>의 JavaScript 코드를 백그라운드 작업으로 다운로드 받습니다. 그래서 다운로드 받는 만큼의 시간을 절약할 수 있습니다. async의 특징은 JavaScript 코드를 완전히 다 받으면 바로 실행되는 것입니다. 그래서 만약 웹페이지가 컨텐츠가 많아 HTML 파싱이 다 끝나지 않았을 경우, HTML 파싱을 중단하고 JavaScript 코드가 실행되고 나서 나머지 HTML 파싱이 진행됩니다. 이 경우, 나머지 HTML 파싱 부분과 관련된 JavaScript 코드가 있을 경우 오류가 날 수 있으며, 나머지 HTML 파싱이 진행되지 않을 수 있습니다.

.

defer

다음은 <script src=”jslibrary.js” defer>를 사용했을 때의 웹 페이지가 렌더링 되는 순서입니다.

.

async를 단점을 보완해서 HTML 파싱하다가 <script>를 백그라운드에서 다운로드 받으면서 HTML 파싱을 계속하고 <script> 를 다운로드 받은 후 DOM 이 엄청나게 크더라도 모든 HTML 파싱이 완전히 끝나기까지 기다렸다가 실행됩니다.

.

다음은 <body> 태그 마지막에 <script src=”jslibrary.js”> 태그를 사용했을 경우입니다.

모든 HTML 파싱이 거의 완료되고 나서 <script> 태그를 보고 다운로드를 하고나서 실행합니다. 이 경우에는 다운로드 시간을 절약할 수 없어서 그만큼 로딩 시간이 길어지게 됩니다.

.

단순한 웹페이지를 제외하고 웹페이지에 단 하나의 JavaScript 라이브러리 파일이 추가되는 경우는 거의 없습니다. 어떤 라이브러리 파일은 300kb가 넘기도 하고 어떤 라이브러리 파일은 50kb, 3kb 만큼 작기도 합니다. 이렇게 작을 경우, async 를 사용해서 script를 로드할 경우 파일들이 서로 상관관계를 가질 경우 문제가 생길 수 있습니다. 다운로드가 완료된 파일을 먼저 실행하기 때문입니다.

.

그래서 async는 다른 스크립트의 실행에 의존하지 않는 스크립트의 경우 많이 사용되고, 다른 스크립트의 실행에 의존하는 스크립트의 경우 순서에 맞춰 defer를 사용합니다. defer를 사용하면, <script>가 작성된 순서로 절차적으로 실행이 되기 때문에 오류가 날 가능성이 적어지게 됩니다.

.

이렇게 JavaScript 파일을 사용할 때 <script> 태그 처리 방법에 대해서 알아봤습니다.

감사합니다. 🙂

Leave a Reply