요즘은 정말 신기한 스크립트들이 많다. 다양한 기능을 지원하고 그것도 오픈소스로 제공하는 것들이 많다. 이렇게 신기한 기능들을 지원하다 보니 모던 웹 브라우저에서 지원하는 스크립트들은 대부분 HTML 보다 무거운게 사실이다. 용량이 크기 때문에 로딩하는데 많은 시간이 걸리고 처리하는 것 역시 마찬가지다.
Script 로딩 이슈
브라우저는 HTML을 읽다가 <script>..</script> 태그를 발견하면 스크립트를 먼저 실행하기 때문에 DOM 생성을 잠시 멈춘다. 이것은 src 속성이 있는 외부 스크립트를 만났을 때도 마찬가지다. 외부 파일에서 스크립트를 다운받아 실행한 후에 남은 DOM을 생성처리 할 수 있다.
이런 브라우저의 동작 방식으로 인해 두 가지 중요한 이슈가 발생한다.
- 스크립트에서는 스크립트 아래에 있는 DOM 요소에 접근할 수 없다 (DOM이 생성되지 않았기 때문에). 따라서 DOM 요소에 핸들러를 추가하는 것과 같은 행동이 불가능하다.
- 페이지 위쪽에 용량이 큰 스크립트가 있는 경우, 스크립트로 인해 페이지 로딩이 느려지게 된다. (정확히는 로딩이 막힌다)
이를테면 아래 코드와 같은 상황일 것이다.
<span>...Hello Soopiri...</span>
<script src='./script/super_very_long.js' />
<!-- Script 다운로드 및 실행이 끝나기 전까지 아래의 내용이 보이지 않는다 -->
<span>...Bye Soopiri...</span>
이런 상황을 피할 수 잇는 몇 가지 방법들이 있다. 아래의 예시처럼 스크립트를 페이지 맨 하단에 위치하는 것이 하나의 방법이 될 수 있다. 이렇게하면 DOM요소에도 접근할 수 있고 페이지의 콘텐츠 출력을 막지 않을 수 있다.
<body>
<span>...Hello Soopiri...</span>
<span>...Bye Soopiri...</span>
<script src='./script/super_very_long.js' />
</body>
하지만 이 방법은 완벽한 해결책이 아니다. Script 로딩 전에 Script에 정의 된 함수를 호출해버리려고 하는 경우 오류가 발생할 수 있으며, HTML과 sciprt 모두 큰 사이즈일 경우, 페이지가 정말 느려질 수 있다.
물론 네트워크 속도가 빠른 곳에서는 이런 상황이 발생할 확률이 드물지만, 네트워크 환경이 열악한 곳이나 모바일 네트워크 접속이 느린 경우에 이런 상황이 발생할 수 있다.
다행히 이런 문제를 유연하게 처리할 수 있는 attribute가 있다. 바로 defer와 async다.
defer, async
defer
브라우저는 defer attribute가 있는 스크립트를 'Background'에서 다운로드 한다. 따라서 defer 스크립트를 다운로드 하는 와중에도 HTML 파싱이 멈추지 않는다. 그리고 defer 스크립트 실행은 페이지 구성이 끝날때까지 지연된다.
위의 예시와 동일한 코드에 defer를 붙여보자.
<span>...Hello Soopiri...</span>
<script defer src='./script/super_very_long.js' />
<!-- 아래 내용이 바로 보인다 -->
<span>...Bye Soopiri...</span>
defer 스크립트는 페이지 생성을 막지 않으며, DOM이 준비된 후에 실행된다. 정확히는 DOMContentLoaded 이벤트 발생 전에 실행된다. 아래의 예시를 살펴보자.
<span>...Hello Soopiri...</span>
<script>
document.addEventListener('DOMContentLoaded', ()=> {
alert('defer script is executed and DOM is ready.')
});
</script>
<script defer src='./script/super_very_long.js' />
<span>...Bye Soopiri...</span>
위의 코드에서는 페이지 콘텐츠는 바로 출력된다. 그리고 DOMContentLoaded 이벤트는 defer 스크립트의 실행을 기다린다. 따라서 alert 창은 DOM이 완성되고, defer스크립트가 실행된 후에 표시된다.
작은 스크립트는 먼저 다운되지만, 실행은 나중에 된다. defer 스크립트는 일반 스크립트와 마찬가지로 HTML에 추가된 순으로 실행된다. 따라서 길이가 긴 스크립트가 앞에, 길이가 짧은 스크립트가 뒤에 있어도 짧은 스크립트는 긴 스크립트가 실행될 때까지 기다린다.
// 먼저실행
<script defer src='./script/super_very_long.js' />
// 위의 스크립트 실행 대기
<script defer src='./script/very_long.js' />
그리고 defer attribute는 외부 스크립트에만 유효하다는 점을 기억하자. 즉, src가 없으면 defer attribute는 무시된다.
async
async attribute가 있는 스크립트는 페이지와 완전히 독립적으로 동작한다.
- async 스크립트는 defer 스크립트와 마찬가지로 백그라운드에서 다운로드 된다. 하지만 async 스크립트 실행중에는 HTML 파싱을 멈춘다.
- DOMContentLoaded 이벤트와 async 스크립트는 서로를 기다리지 않는다.
- 페이지 구성이 끝난 후에 async 스크립트 다운로딩이 끝난 경우, DOMContentLoaded는 async 스크립트 실행 전에 발생할 수 있다.
- async 스크립트가 짧아서 페이지 구성이 끝나기 전에 다운로드 되거나 스크립트가 캐싱처리 된 경우, DOMContentLoaded는 async 스크립트 실행 후에 발생할 수도 있다. - 다른 스크립트들은 async 스크립트를 기다리지 않는다. async 스크립트도 다른 스크립트를 기다리지 않는다.
이런 특징 때문에 페이지에 async 스크립트가 여러 개 있는 경우, 그 실행 순서는 제가각이 된다. 실행은 다운로드가 완료된 스크립트 순으로 진행된다. 아래의 코드를 살펴보자.
<span>...Hello Soopiri...</span>
<script>
document.addEventListener('DOMContentLoaded', ()=> {
alert('DOM is ready.')
});
</script>
<script async src='./script/super_very_long.js' />
<script async src='./script/very_long.js' />
<span>...Bye Soopiri...</span>
- 페이지 콘텐츠는 스크립트 다운로드 여부와 관계없이 바로 출력된다.
- DomContentLoaded 이벤트는 상황에 따라 async 스크립트 전/후에 실행된다. 정확한 순서를 예측할 수 없다.
- 비동기 스크립트는 서로를 기다리지 않는다. very_long이 먼저 다운로드 되면 먼저 실행된다. 이를 "load-first order" 라고 부른다.
비동기 스크립트는 방문자 수 카운터, 광고 관련 스크립트처럼 각각 독립적인 역할을 하는 3rd party script를 현재 개발중인 스크립트에 통합하려고 할 때 매우 유용하다. async 스크립트 자체가 개발 중인 스크립트에 의존하지 않고, 그 반대도 마찬가지기 때문이다.
요약
defer와 async 스크립트는 다운로드 할 때 페이지 렌더링을 막지 않는다는 공통점이 있다. 따라서 async와 defer를 적절히 사용하면 사용자가 오래 기다리지 않고 페이지 콘텐츠를 볼 수 있게 한다.
'Development > JavaScript' 카테고리의 다른 글
Javascript - 원시값(Primitive)의 메서드 (0) | 2022.04.27 |
---|---|
Javascript - Set을 알아보자 (0) | 2022.04.27 |
Javascript - 맵(Map)을 알아보자 (0) | 2022.04.27 |
구조 분해 할당(Destructuring Assignment) - 객체, 중첩구조 편 (0) | 2022.04.26 |
구조 분해 할당(Destructuring Assignment) - 배열편 (0) | 2022.04.25 |