JavaScript/JS

<script> async/ defer란? (스크립트 파일 동작, 로드시기 제어)

hihiha2 2023. 9. 21. 20:13

➡️ <script> 태그의 속성으로, 스크립트 파일의 동작로드 시기를 제어한다.

 




1. 기능

script 파일의 동작/로드 시기 제어

 

 

2. 방법

html의 <script>태그안에 async, defer 표기

    <script src="해당경로" async></script>
    <script src="해당경로" defer></script>

 

async와 defer를 제대로 이해하기 위해서는 HTML의 실행순서에 대한 이해가 선행되어야 한다.

이런 이해가 바탕에 있어야, 왜 굳이 async와 defer를 사용해야하는지  그 필요성에 대해서 알 수 있다.

 

 

 

🧷 선행학습

🔎 HTML의 문서파싱 순서와 동작원리

 

html은 기본적으로 위에서부터 아래로 태크를 해석한다.

위에서부터 태그를 해석하면서 돔트리를 형성한다.

그렇게 형성된 트리에 의해서 유저는 브라우저 화면을 통해서 개발자가 만든 화면을 볼 수 있다.

 

 

🕵️‍♀️ 발생가능 문제점 2가지

이러한 HTML의 성질때문에 문제가 발생할 수 있다.

위에서부터 순차적으로 태그를 파싱하므로 body태그의 위에 위치한 <script>태그를 body보다 먼저 해석하기 시작한다.

script태그가 모두 수행될때까지 아래로 더이상 나아가지 않고 멈추고,

script를 모두 파싱한 뒤에야 아래로 넘어가서 html의 태그의 파싱을 다시 시작한다.

 

 

⛔️ 발생가능 상황 1️⃣ : 시간이 delay되는 작업일 때

그런데 script태그안에는 API를 호출해서 데이터를 받아오는 것과 같이 시간이 꽤 걸리는 작업이 포함될 수 있다.

➡️ 문제점 1️⃣ : script태그안의 파일을 모두 파싱할 때까지 HTML은 동작을 멈춘다!!

 

이렇게 되면, js가 일을 다 끝낼때까지 delay가 발생하고 유저는 아무것도 나오지 않는 빈화면을 보면서 기다려야 한다.

(사용자의 인터넷 환경이 느린 경우, js에서 받아올 파일이 매우 큰 경우 부적절)

 

 

⛔️ 발생가능 상황 2️⃣ : js에서 HTML태그 접근❌

js파일에서 html파일의 태그를 이용해서 변화를 주려고 해도 html파일의 태그가 아직 생성되지 않았기때문에 불러올 수 없다.

➡️ 문제점 2️⃣: js에서 html의 태그를 불러오지 못하여 에러 발생!!

 

쉽게 설명하면, js에서 어떤 특정 이벤트를 처리하기 위해서 html의 태그를 연결해야한다.

 

예를 들면

html에서 id=container라는 태그에 특정 이벤트를 처리하기 위해서 

js에서 getElementById를 이용해서 html의 해당 태그에 먼저 접근해야 한다.

 

하지만 script가 먼저 파싱되는 상황에서는 container라는 id를 가진 값에 이벤트처리를 해주어도,

아직 html태그가 파싱되지 않았기 때문에 js는 해당 태그를 찾을수가 없다!!

 

그러면 아래와 같은 에러가 나온다.

 

 

 

 

✅ 해결법 3가지

1️⃣ script파일을 html의 아래쪽으로 위치시키기

head태그 안의 script태그를 body태그를 닫기 직전의 위치로 하단에 위치시켜서,

가장 마지막에 js파일을 파싱하도록 한다. 

 

이렇게 하면 해당 태그를 찾지 못해서 발생하는 에러를 사라지게 할 수 있다.

before / after

 

하지만, 이러한 방식에도 단점이 존재한다.

 

HTML의 파싱이 모두 끝난 뒤 js의 파싱이 시작된다. 

사용자는 HTML요소들은 미리 보면서 js의 패칭을 기다릴 수 는 있지만, js안에 유의미한 데이터들이 많은 페이지의 경우에는 사용자가 js를 기다리는 시간이 길어진다.

(HTML의 화면은 볼 수 있지만, 알맹이는 못보는 것)

 

 

 

2️⃣ <head>안에 async

<script>를 <head>태그 안에 위치시키고 async를 속성으로 적는다.

 

html의 파싱js의 파싱병렬적으로 동시에 일어난다.

병렬적으로 파싱하다가 js의 파싱이 모두 끝나면 html의 파싱을 멈추고 (blocked), 다운로드된 js파일을 실행한다.

js를 실행하고 나서 나머지 html를 다시 파싱하기 시작한다.

 

이 방법 역시 단점이 존재한다.

 

⛔️ 발생가능 상황 2️⃣ :js에서 HTML태그 접근❌와 같은 상황

html이 모두 다운로드되지 않은 상황에서 js를 조작하므로 원하는 html의 요소가 아직 정의되지 않을 수 도 있다.

querySelect와 같이 돔요소의 조작을 하려고 하면, 해당 태그가 없다는 에러가 발생할 수 있다. 

 

또한 js를 실행하는 과정에서 html을 멈추므로 (blocked) 사용자가 페이지를 보는데 시간이 delay되는 단점도 존재한다.

 

 

⭐️ 3️⃣ <head>안에 defer ⭐️

<script>를 <head>태그 안에 위치시키고 defer를 속성으로 적는다.

html과 js의 파싱이 동시에 일어난다.

 

html이 모두 다 다운로드 된 다음, 사용자에게 화면을 보여주고나서(page is ready) js를 실행한다.

 

 

 

 

 

🔫 async와 defer의 비교

async: 정의된 순서와 상관없이, 먼저 다운로드된 js를 먼저 실행한다. ➡️ 순서문제 발생 가능성

defer: js를 모두 받은 뒤에 순서대로 js가 실행되므로 원하는 순서대로 순차적으로 실행되게 할 수 있다.

 

 

 

 

 결론

⭐️ <head>안에 defer ⭐️가 best!!

html과 js 병렬적으로 동시에 파싱하면서도, html이 모두 파싱뒤에야 js가 실행되므로 

 

⛔️ 발생가능 상황 1️⃣ : 시간이 delay되는 작업일 때

⛔️ 발생가능 상황 2️⃣ : js에서 HTML태그 접근❌

의 문제를 모두 해결할 수 있다.

 

또한 여러개의 script를 연결할때도 의도한대로 순차적으로 순서가 실행되도록 함으로써 안정성을 높인다.

 

 

 

🙋‍♀️ 내 생각

이 글을 쓰게 된 계기는 사실 자바스크립트로 돔요소를 조작하는 것을 공부하면서 만난 에러때문이다.

querySelector를 통해서 html의 태그에 접근해서 특정한 이벤트를 수행하게 만들려고 했는데 아래와 같은 에러가 발생하였다.

Uncaught TypeError: Cannot read properties of null (reading 'addEventListener')

위에 적은 2번째 html의 해당태그를 찾지 못하는 것에 해당하는 문제점이다.

 

해결법은 script태그에 defer를 적는 것과 같이 매우 간단했는데, 왜 이런 문제가 발생하는지 또 왜 이런 방법을 통해서 해결이 가능한지를 이해해야 앞으로 이런 문제점을 만났을때 응용이 가능하리라 생각해서 공부해보았다.

 

리액트를 제대로 이해하기 위해서는 js에 대한 이해가 필요한데, js도 결국 html을 조작하는 것이기때문에 html의 실행순서와 같은 기본적인 것들에 대한 이해도 올바르게 되어있어야 할 것 같다는 생각이 들었다.

 

js에서도 실행순서가 중요한데 html도 유사한 동작방법을 지니고 있는 것 같다고 생각했다.

 

 

 

 

참고 (8:40~)

https://youtu.be/tJieVCgGzhs?si=XCTxNRUG4-4HGtPc