과거의 내가 미래의 나에게
script 태그의 삽입 위치에 대하여 본문
script 태그를 삽입했는데 아니 글세
요새 바닐라 js만으로 기능을 개발해보는 작업을 하고 있다. 별 어려움없이 DOM객체를 활용하여 이벤트도 넣고 기능도 넣고... 그렇게 js 파일을 작성한 후 HTML 파일의 head에 삽입한 후 실행했다. 그러나 js 코드가 일부 작동조차 하지 않는 것을 발견하고 왜 함수가 실행이 안되지 갸웃갸웃하다 검색을 해보니 script 태그의 위치를 올바르게 삽입하지 않았다는 것을 깨달았다. 덕분에 이런 기초적인 것도 몰랐던 것에 반성하고 기록해둔다.
왜 작동이 안되었을까?
브라우저가 렌더링 되는 순서는 아래와 같다. 이는 간단하게 적은 것이니 꼭 따로 살펴보도록 하자.
- HTML 파일을 파싱
- DOM 트리 생성
- 렌더 트리 생성
- 화면에 표출
브라우저가 HTML 파일을 해석하기 위해 파싱하게 되는데 위에서 아래로 쭉 내려오다가 js script 태그를 만나면 HTML 파싱을 중단하고 js 파일을 로드 및 파싱하게 된다. js 파일이 파싱되는 동안 HTML 파싱은 중단되어 있는 상태이고, js 파싱이 끝나면 다시 재개된다.
이 경우, script는 삽입된 script 아래에 있는 DOM 요소에 접근할 수 없다. 따라서 DOM 요소에 이벤트를 추가하는 등이 불가해지는 것이다.
또한 용량이 큰 script가 위에서 로드 및 파싱되는 동안 HTML 파싱은 중단됨으로 사용자들은 그 동안 화면을 볼 수 없게 되는 것이다..
그렇다면 어떻게?
script를 막힘없이 진행시키기 위해서는 아주 간단한 방법으로, script 태그의 위치를 body 태그 안의 가장 하단에 두는 것이다. 그럼으로 인해 DOM 요소에 접근하려는 시도가 막힐 리도 없고 또 사용자도 DOM 객체가 다 생성되어있을 테니 상대적으로 빠르게 화면을 볼 수 있을 것이다.
하지만 이 경우에도 결국 완벽한 해결책은 아닌데, 만약 HTML 파일이 엄청 크다면 해당 HTML 파일이 로드 파싱되는 것을 기다린 후 또 script 태그까지 별도로 로드 파싱해야하니 페이지가 정말 느려질 것이다.
<script> 속성을 이용하여 로드하기
위의 body 하단에 태그를 놓는 방법 말고도 다른 방법도 있다. defer 속성 혹은 async 속성을 사용하여 로딩 순서를 제어하는 것이다.
1. defer
브라우저는 defer 속성이 있는 스크립트를 백그라운드에서 따로 다운로드 한다. 따라서 defer script를 다운로드 하는 도중에도 HTML 파싱이 멈추지 않고 HTML 파싱이 끝나고 나서야 defer 스크립트가 실행된다. 따라서 화면은 빠르게 띄울 수 있지만 아직 script 실행이 덜 되었을 수도 있으니 사용자 조작에 유의 해야 할 것이다.
여러 script가 백그라운드에서 로드 될 때는 가장 짧은 것 먼저 로드 되지만, 실행은 짧고 긴 것과 상관없이 script를 문서에 추가한 순서대로 실행된다.
<script defer src="main.js">
1. HTML 파싱
2. script 태그 발견
3. HTML 파싱 + script 로드 후 대기
4. HTML 파싱 종료
5. script 실행
2. async
async 스크립트는 defer 스크립트와 마찬가지로 백그라운드에서 다운로드된다. 따라서 마찬가지로 script를 다운로드 하는 도중에서 HTML 파싱은 멈추지 않는다.
다만 script 로드가 완료되면 그 순간 HTML 파싱을 멈추고 script 실행을 한다. 실행 순서는 로드가 완료된 순간이기에 script가 길고 짧고 상관없이 먼저 로드된 순서대로 실행한다. 따라서 script끼리의 순서를 파악하기는 어려울 것이다.
<script async src="main.js">
1. HTML 파싱
2. script 태그 발견
3. HTML 파싱 + script 로드
4. script 로드 완료 시 HTML 파싱 중단 +script 실행
5. script 실행 완료 시 HTML 파싱 재개
스크립트 내부에서 로딩 순서 제어하기
DomContentLoaded과 onload를 활용할 수도 있다.
1. DomContentLoaded
브라우저가 HTML을 전부 읽고 DOM 트리를 완성하는 즉시 발생한다. 이미지 파일이나 css 등의 기타 자원은 기다리지 않는다. img 태그에는 접근할 수 있지만 img의 크기나 기타 정보에 대해서는 아직 로드되지 않았기에 알 수 없다.
document.addEventListener('DOMContentLoaded', function() {
console.log("DomContentLoaded")
}
2. onload
HTML로 DOM 트리를 만드는 게 완성되었을 뿐만 아니라 이미지, css 같은 외부 자원도 모두 불러오는 것이 끝났을 때 발생한다. 스타일까지 모두 입혀 있기에 화면에 실제로 뿌려진 크기나 위치 등을 파악할 수도 있다.
window.onload = function() {
console.log("onload")
}
참고 문서
- 브라우저 렌더링 과정 이해하기. - https://tecoble.techcourse.co.kr/post/2021-10-24-browser-rendering/
- <script> 태그는 어디에 위치해야 할까요 - https://velog.io/@takeknowledge/script-%ED%83%9C%EA%B7%B8%EB%8A%94-%EC%96%B4%EB%94%94%EC%97%90-%EC%9C%84%EC%B9%98%ED%95%B4%EC%95%BC-%ED%95%A0%EA%B9%8C%EC%9A%94
- defer, async 스크립트 - https://ko.javascript.info/script-async-defer
'js 이론' 카테고리의 다른 글
딥다이브 :: 함수 (0) | 2023.01.24 |
---|---|
딥다이브 :: 객체 리터럴 & 원시 값과 객체의 비교 (1) | 2023.01.13 |
딥다이브 :: 데이터 타입 & 연산자 & 제어문 & 단축평가 (0) | 2023.01.01 |
딥다이브 :: 변수 & 표현식과 문(state) (0) | 2022.12.28 |
딥다이브 :: JS에 배경 지식에 대하여 새로 안 것들 (0) | 2022.12.27 |