웹 브라우저는 html을 랜더링하는 과정에서 css(<link type='text/css'>
) 또는 js(<script>
) 를 만나면 동기적으로 처리한다. 다시 말해 해당 내용이 해석되고 실행되기 전에는 뒤에 나오는 내용을 처리하지 않는다는 것이다. 이 부분은 화면의 랜더링 속도에 큰 영향을 줄 수 있다(사용자 경험 측면에서 큰 영향을 주며 SEO와도 관련된다). 이와 관련된 자세한 내용은 HTML Critical rendering path의 이해라는 글에 있으니 필요하면 참고 바란다.
css의 경우는 화면을 랜더링하는데 필요한 정보를 담고 있으므로 해당 내용을 출력하기 전에 해석되는 것이 당연히 유리하다(화면이 여러번 랜더링되는 것을 줄일 수 있다). css에 대한 부분도 최적화를 위해 화면에 보여지는 영역과 숨겨진 영역에 대한 css를 분리 호출하는 것을 권장하는 등 최적화에 관련된 여러가지 이슈가 있다. 하지만 이 부분은 여기서 다루고자하는 내용이 아니므로 일단 넘어가자.
js의 경우 대부분 화면 출력과 관련되기 보다 기능적 처리에 관련된 경우가 많다. 웹앱 등의 경우 출력과 직접적인 관련이 있을 수도 있으나 이 또한 기본적인 화면이 출력된 이후에 처리되는 것이 웹 페이지를 빠르게 랜더링하는데 유리하기 때문에 처리를 지연하는 것이 좋다.
이러한 이유로 대부분의 경우 css는 <head>
영역에 js는 </body>
바로 앞에 선언하는 것을 추천한다. 이 방식은 아래에 기술하겠지만 오래된 브라우저에서도 동일한 효과를 얻는 좋은 방법이 될 수 있다. 하지만 이 방법 외에도 <script>
에는 async 속성과 defer 속성을 사용하는 방법이 있다. 이 글에서는 이 두가지 속성에 대해서 이야기 하고자 한다.
일반적인 실행
async와 defer의 동작에 대해 알아보기 전에 기본적인 <script>
의 실행 과정에 대해 알아보자. 기본적으로 <script>
는 인라인 코드의 경우 즉시 해석되고 실행될 수 있지만 그렇지 않은 경우는 해당 파일을 가져올 때까지 HTML 문서의 구문 분석을 중단한다.
위 그림에서 보여주듯이 스크립트를 가져 와서 실행하기 위해 HTML 구문 분석이 일시 중지되므로 HTML이 화면에 출력되는 시간이 길어진다.
async 속성이 추가된 경우의 실행
async 속성은 브라우저에 스크립트 파일이 비동기적으로 실행될 수 있음을 나타내기 위해 사용된다. HTML 구문 분석기는 스크립트 태그에 도달한 지점에서 스크립트를 가져오고 실행하기 위해 일시 중지 할 필요가 없다. 따라서 HTML 구문 분석과 병행하여 스크립트를 가져온 후 스크립트가 준비 될 때마다 즉시 실행이 가능하다. 그러므로 실행의 순서가 다운로드 완료 시점의 결정되므로 실행 순서가 중요한 스크립트들에 async를 사용할 때는 유의해야 한다(HTML5 spec에 async=false 속성 지정시 호출 순서대로 실행되도록 추가됨(default : true)).
<script async src="script.js">
이 속성은 외부에 위치한 스크립트 파일에서만 사용할 수 있다. 외부 스크립트에 이 속성이 있으면 HTML 문서가 여전히 구문 분석되는 동안 파일을 다운로드 할 수 있으며 다운로드가 완료되면 스크립트가 실행될 수 있도록 구문 분석이 일시 중지 된다.
defer 속성이 추가된 경우의 실행
defer 속성은 HTML 구문 분석이 완전히 완료되면 스크립트 파일을 실행하도록 브라우저에 지시한다.
<script defer src="script.js">
비동기적으로 로드된 스크립트와 마찬가지로, HTML 구문 분석이 실행되는 동안 파일을 다운로드 할 수 있다. 그러나 HTML 구문 분석이 완료되기 전에 스크립트 다운로드가 완료 되더라도 구문 분석이 완료 될 때까지 스크립트는 실행되지 않는다. 또한, async와는 다르게 호출된 순서대로 실행된다.
언제 사용할 것인가?
일반적인 스크립트 실행과 aync, defer 실행을 결정하기 위해서는 몇가지 확인해야 할 사항이 있다.
<script>
요소는 어디에 있는가?
<script>
요소가 문서 맨 끝에 있지 않으면 스크립트의 비동기 및 지연 실행이 더 중요하다. HTML 문서는 첫 번째 여는 <html>
요소부터 닫히는 순서로 파싱됩니다. 외부 소스 JavaScript 파일이 닫는 </body>
요소 바로 앞에 있으면, async 또는 defer 속성을 사용하는 것이 큰 효과가 없다(HTML 파서가 그 시점까지 문서의 대다수를 완성 했기 때문에 지연에 의미가 크게 없다는 것이다).
스크립트 자체가 포함되어 있는가?
다른 파일들에 종속적이지 않거나 종속성 자체가 없는 스크립트 파일의 경우 async 속성이 특히 유용하다. 파일이 어느 지점에서 실행되는지 정확히 알 필요가 없기 때문에 비동기 로드가 가장 적합하다.
스크립트가 완전히 구문 분석 된 DOM에 의존하는가?
대부분의 경우 스크립트 파일에는 DOM과의 상호 작용이 필요한 기능이 포함되어 있거나 페이지에 포함된 다른 파일에 대한 종속성이 있을 수 있다. 이러한 경우 스크립트를 실행하기 전에 DOM이 완전히 해석되어야 정상적인 동작을 할 수 있다. 일반적으로 이러한 스크립트 파일은 페이지의 맨 아래에 배치되어 모든 내용이 파싱된 후에 동작하도록 해야 한다. 그러나 어떤 이유로 든 문제의 파일을 다른 위치에 배치해야 하는 상황에서는 defer 속성을 사용할 수 있다.
스크립트가 작고 종속성을 가지는가?
마지막으로 스크립트가 비교적 작고 다른 파일에 의존하는 경우 인라인으로 정의하는 것이 더 유용 할 수 있다. 인라인을 사용하면 HTML 문서의 구문 분석이 차단되지만 크기가 작으면 큰 문제가 되지 않는다. 또한 다른 파일에 의존하는 경우 차단이 필요할 수 있다.
async와 defer를 지원하는 브라우저
async 및 defer 속성은 최신 브라우저를 기준으로 보자면 매우 광범위하게 지원된다. 단, 이러한 속성의 동작은 JavaScript 엔진마다 약간 다를 수 있다. 예를 들어 V8(Chromium에서 사용됨)에서는 스크립트 실행을 위한 별도의 전용 스레드에서 속성에 관계없이 모든 스크립트를 구문 분석하려고 한다(New JavaScript techniques for rapid page loads).
지원하는 브라우저를 알아보기 전에 일부만 지원되는 경우에 대한 동작 과정을 알아보자. defer 속성만 있다면 스크립트는 페이지의 파싱이 완료된 후에 실행된다. 단, async와 defer 속성이 모두 지정된 경우 async 속성을 지원하는 최신 브라우저는 기본적으로 async 속성을 따른다. 하지만 async 속성을 지원하지 않는 구형 브라우저는 defer 속성의 지원 여부에 따라 결과가 다르다. defer 속성을 지원하는 경우 defer 속성에 의해 비동기적으로 스크립트를 실행한다. defer 조차도 지원하지 않는 구형 브라우저는 동기적으로 스크립트를 실행한다.
- IE는 defer의 경우 예전부터 부분 지원하고 있으나 async 속성은 10 버전 이상부터 지원(defer 완전 지원 포함)
- Firefox는 3.6 버전부터 모두 지원
- chrome은 8 버전부터 모두 지원
- safari는 5 버전부터 모두 지원(단, 5버전에서는 async=false 지원 안함)
- ios safari는 5.1 버전부터 모두 지원
- android는 3 버전부터 모두 지원
결론적으로는 원만하면 다 지원한다. IE의 경우가 예외적이긴 하지만 IE 10 미만의 점유율이 극히 낮아진 이 시점에서 무시해도 될 듯하다(굳이 지원해야 한다면 aync, defer 속성을 사용하기 보다 </body>
바로 앞에 스크립트를 위치시키는 방법이 가장 낫다).
보다 상세한 지원 여부는 아래의 caniuse.com 을 참고하자.