HTML이 렌더링 중에 JavaScript가 실행되면 멈추는데 그 이유는 무엇일까요?
면접용
HTML이 렌더링되는 도중에 JavaScript가 실행되면 브라우저는 렌더링을 멈추고 JavaScript 코드를 먼저 실행합니다. 그 이유는 브라우저가 HTML을 해석하고 화면을 그리는 과정(렌더링)과 JavaScript 실행이 동일한 메인 스레드에서 처리되기 때문입니다.
자바스크립트는 기본적으로 싱글 스레드 환경에서 동작하기 때문에, 실행 중인 코드가 끝나야 다음 작업을 진행할 수 있습니다. 그래서 HTML을 파싱하는 도중 태그를 만나면, 브라우저는 먼저 해당 JavaScript를 실행하고, 실행이 끝난 후에야 다시 HTML 파싱을 이어갑니다.
이 때문에 스크립트 실행이 오래 걸리면 화면 렌더링이 지연될 수 있습니다. 이를 방지하기 위해 defer 또는 async 속성을 사용하면 브라우저가 HTML을 계속 파싱하면서 JavaScript를 비동기적으로 실행할 수 있습니다.
개념 설명
JavaScript 실행과 HTML 파싱의 관계
브라우저는 기본적으로 **싱글 스레드(Single Thread)**에서 동작합니다. 즉, 한 번에 하나의 작업만 실행할 수 있습니다. 이 때문에 HTML을 파싱하는 작업과 JavaScript를 실행하는 작업이 같은 메인 스레드에서 처리됩니다.
브라우저가 HTML을 읽는 도중 <script>
태그를 만나면, HTML 파싱을 멈추고 해당 JavaScript를 먼저 다운받고 실행합니다. JavaScript 실행이 완료된 후에야 다시 HTML을 해석하고 화면을 렌더링할 수 있습니다. 이 과정에서 JavaScript 실행 시간이 길어지면 렌더링이 지연될 수 있으며, 사용자가 빈 화면을 보게 되는 경우도 발생할 수 있습니다.
이런 브라우저의 동작 방식은 두 가지 중요한 이슈를 만듭니다.
스크립트에서는 스크립트 아래에 있는 DOM 요소에 접근할 수 없습니다. 따라서 DOM 요소에 핸들러를 추가하는 것과 같은 여러 행위가 불가능해집니다.
<!DOCTYPE html> <html lang="ko"> <head> <title>DOM 접근 문제</title> <script> document.getElementById("myButton").addEventListener("click", function() { alert("버튼 클릭됨!"); }); </script> </head> <body> <!<script>가 실행될 때 아직 <button> 요소가 생성되지 않았기 때문에 오류 발생> <button id="myButton">클릭하세요</button> </body> </html>
페이지 위쪽에 용량이 큰 스크립트가 있는 경우 스크립트가 페이지를 ‘막아버립니다’. 페이지에 접속하는 사용자들은 스크립트를 다운받고 실행할 때까지 스크립트 아래쪽 페이지를 볼 수 없게 됩니다.
해결 방법
이러한 문제를 방지하기 위해 <script>
태그에 async
또는 defer
속성을 추가하면 JavaScript가 HTML 파싱을 막지 않도록 조정할 수 있습니다. Javascript 다운은 네트워크 스레드가 실행하기 때문에 병렬로 다운받을 수 있습니다. 기존에는 script.js를 만나면 다운받고 실행해서 다운받을 js 용량이 크거나 하면 성능 저하가 있을 수 있었는데, async, defer는 script.js를 병렬로 다운받아 문제를 해결합니다.
async
JavaScript 파일을 HTML과 병렬로 다운로드한 후, 다운로드가 완료되는 즉시 실행합니다. 하지만 실행 순서는 보장되지 않으므로, 스크립트 간의 의존성이 있을 경우에는 주의해야 합니다.
<!DOCTYPE html> <html lang="ko"> <head> <meta charset="UTF-8"> <title>JavaScript 실행 테스트</title> <script src="script.js" async></script> <!-- HTML과 병렬로 다운로드, 다운로드 후 즉시 실행 --> </head> <body> <h1>안녕하세요</h1> <p>이 문장은 script 실행과 관계없이 렌더링될 수 있습니다.</p> </body> </html>
defer
JavaScript 파일을 HTML과 병렬로 다운로드하면서도, HTML 파싱이 완전히 끝난 후 실행됩니다. 따라서 DOM을 조작하는 코드가 있을 때 적합하며, 여러 개의
defer
스크립트는 작성된 순서대로 실행됩니다.<!DOCTYPE html> <html lang="ko"> <head> <meta charset="UTF-8"> <title>JavaScript 실행 테스트</title> <script src="script.js" defer></script> <!-- HTML과 병렬로 다운로드, HTML 파싱 후 실행 --> </head> <body> <h1>안녕하세요</h1> <p>이 문장은 script 실행 전에 이미 렌더링될 수 있습니다.</p> </body> </html>
DOMContentLoaded
이벤트defer
속성과 유사한 방식으로, HTML이 완전히 파싱된 후 JavaScript를 실행하고 싶다면DOMContentLoaded
이벤트를 사용할 수도 있습니다.HTML이 모두 로드되고,
defer
스크립트까지 실행된 후DOMContentLoaded
이벤트가 발생합니다.<!DOCTYPE html> <html lang="ko"> <head> <meta charset="UTF-8"> <title>JavaScript 실행 테스트</title> </head> <body> <h1>안녕하세요</h1> <script> document.addEventListener("DOMContentLoaded", function() { document.getElementById("myButton").addEventListener("click", function() { alert("버튼 클릭됨!"); }); }); </script> <button id="myButton">클릭하세요</button> </body> </html>
Last updated