JAVASCRIPT - 문서와 리소스 로딩

Resource 란 ?

일반적으로 리소스란, 사용될 수 있는 어떤 항목을 말한다. 프린터나 디스크 드라이브와 같은 장치들이 리소스가 될 수 있으며, 메모리도 마찬가지이다. 마이크로소프트 윈도우나 매킨토시와 같은 많은 운영체계에서, 리소스라는 용어는 특히 프로그램들이 활용할 수 있는 데이터나 루틴들을 가리킨다.
이런 것들을 다른 말로는 '시스템 리소스'라고도 부른다. 커다란 시스템의 일부를 이루는 하드웨어, 소프트웨어, 또는 데이터의 한 구성요소를 말한다.

DOMContentLoaded, load, beforeunload, unload 이벤트

HTML 문서의 생명주기엔 다음과 같은 4가지 주요 이벤트가 관여한다.

⪧ DOMContentLoaded

: 브라우저가 HTML을 전부 읽고 DOM 트리를 완성하는 즉시 발생한다. 이미지 파일(< img >)이나 stylesheet 등의 기타 자원은 기다리지 않는다.

DOMContentLoaded 이벤트는 document 객체에서 발생한다.
즉, 이 이벤트를 다루려면 addEventListener를 사용해야 한다.

다음 예시를 보자.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script>
  function ready() {
  alert('DOM이 준비되었습니다!');

  // 이미지가 로드되지 않은 상태이기 때문에 사이즈는 0x0입니다.
  alert(`이미지 사이즈: ${img.offsetWidth}x${img.offsetHeight}`);
  }
  window.addEventListener("DOMContentLoaded",()=>{
    ready();
  })
  </script>
  <img id="img" src="https://en.js.cx/clipart/train.gif?speed=1&cache=0">
</body>
</html>

HTML의 script태그안에 Javascript를 작성하였고 이미지의 사이즈를 알려주는 alert창을 ready( ) 함수안에 담았고 그 함수를 DOMContentLoaded 이벤트안에서 호출하였다.
img는 script태그 밖에서 작성하였다.

결과를 확인해보자.

화질이 좋지않아 희미하지만 alert창의 이미지 사이즈가 0이 나온 것을 확인할 수 있다.

DOMContentLoaded가 HTML을 전부 읽고 난 후 실행된 것은 맞지만 img태그는 기다리지 않은 경우이다.

⪧ load

같은 예시로 DOMContentLoadedload로 변환해 어떤 변화가 있는지 알아보겠다.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script>
  function ready() {
  alert('DOM이 준비되었습니다!');

  alert(`이미지 사이즈: ${img.offsetWidth}x${img.offsetHeight}`);
  }
  window.addEventListener("load",()=>{
    ready();
  })
  </script>
  <img id="img" src="https://en.js.cx/clipart/train.gif?speed=1&cache=0">
</body>
</html>

결과를 확인해보자. DOMContentLoaded와 어떤 차이가 있을까?

DOMContentLoaded 와는 다르게 img태그가 먼저 실행이 되는 것을 확인할 수 있다. 즉, load 이벤트는 HTML을 전부 읽고 난 후 실행되는 것은 물론 img태그나 css와 같은 리소스를 전부 읽은 후 수행되는 것이다.
또한 DOMContentLoaded를 수행했을 때와는 달리 이미지의 사이즈도 잘 출력되는 것을 볼 수 있다.

어느 것이 우위일까?

DOMContentLoaded 이벤트는 즉, DOM Tree 분석이 (html parasing)이 끝나면 발생하고 load 이벤트는 DOM Tree 이후 모든 리소스까지 ( img태그, stylesheet...) 완벽히 끝난 후 발생하는 이벤트이다.

DOMContentLoaded 이벤트가 load 이벤트보다 앞서 발생하는 사실 또한 알 수 있다.

잠깐 코드를 통해 확인해보면

<script>
    //after resources (css,images)
    window.addEventListener('load',()=>{
      console.log('load');
    })
    // only document
    window.addEventListener('DOMContentLoaded',()=>{
      console.log('DOMContentLoaded');
    })
</script>

순서가 load이벤트가 DOMContentLoaded 보다 앞서지만 결과를 확인해보면

이렇게 DOMContentLoaded 이벤트가 먼저 실행되는것을 알 수 있다.

앞전에 이미지를 불러오는 간단한 예시로써 우리는 DOMContentLoaded 이벤트와 load 이벤트의 차이를 알아보았다.

간단한 예시여서 큰 체감은 오지 않을 수 있지만 둘 중 어떤 이벤트를 써서 유저에게 UX적으로 더 좋은 결과를 보이게 끔 할지는 사실 case by case라고 본다.
물론 위 예시의 경우엔 network를 확인하여 Timing을 비교해본 결과 DOMContentLoaded 이벤트를 사용하였을 경우가 loading에 있어 우위인 것을 확인하였다.

아직 작성자 본인도 이러한 문서 로딩에 있어 확실하게 어떤 상황일때 어떤 이벤트를 써라고 단정짓게 말할 수는 없다. 그렇지만 위의 내용들을 토대로 모든 리소스의 파싱이 완료된 후 자바스크립트 엔진의 동작을 확인하고 싶다면 load 이벤트를 사용하는 것이 적합하고, DOM 트리만 완성이 되면 바로 자바스크립트의 동작을 알고 싶다면 ( 즉, 빠른 실행속도가 필요하다면 ) DOMContentLoaded 이벤트를 사용하는 것이 적합하다 본다.

⪧ beforeunload

: 사용자가 사이트를 떠나려 할 때, 변경되지 않은 사항들을 저장했는지 확인시켜줄 때 사용하는 윈도우 이벤트이다.

작성법은 위의 load 이벤트들과 동일하게 addEventListener을 사용한다.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <p>detect close the browser or refresh</p>
  <form action="">
    <textarea placeholder="input the text and use this page"name="" id="" cols="30" rows="10"></textarea>
  </form>
  <script>
    window.addEventListener('beforeunload', (event) => {     // beforeunload
      // 표준에 따라 기본 동작 방지
      event.preventDefault();
      // Chrome에서는 returnValue 설정이 필요함
      event.returnValue = '';
    });
  </script>
</body>
</html>

HTML 페이지에 addEventListener 함수와 beforeunload 이벤트를 이용해 브라우저를 닫거나, 페이지를 이동 혹은 나가려고 할 때 확인 대화 상자를 표시하도록 작성한 예제이다.

addEventListener의 콜백 함수안에 event.preventDefault()를 사용하여 종료 동작을 방지한다. 또한 Chrome에서는 returnValue의 설정을 요한다. ( 자세한 것은 구글링을 통해 참조 바람 )

실행한 후 과정을 살펴보자.


다음과 같이 text 폼에 '안녕하세요' 라는 text value를 기입하였다.

이상태에서 새로고침 버튼을 눌러보았다.

다음과 같이 새로고침 확인 창이 뜨는 것을 확인할 수 있다.

이번에는 브라우저 닫기 버튼을 눌러보자.

마찬가지로 브라우저 종료 전 마지막 확인을 묻는 메시지가 나오게 된다.

⪧ unload

: 사용자가 진짜 떠나기 전에 사용자 분석 정보를 담은 통계자료를 전송하고자 할 때 ( 서버로 전송 ) 사용되는 이벤트이다. unload 이벤트는 문서나 하위 리소스가 언로딩 중일 때 발생한다.

unload는 다음 이벤트 이후 발생한다.

  • beforeunload

  • pagehide ( 나중에 페이지 캐시에 대한 포스팅 시 다룰 예정 - **bfcache** )

unload 시점의 문서는 다음과 같은 상태이다.

  • 이미지, IFrame 등 모든 리소스는 여전히 존재한다.
  • 최종 사용자는 아무것도 볼 수 없다.
  • UI 상호작용은 아무 효과도 없다.( window.open() , window.alert() , window.confirm() 등등 )
  • 오류가 발생해도 언로딩 절차는 중단되지 않는다.

unload 이벤트 역시 문서 트리의 순서를 따라갑니다. 즉 부모 프레임의 unload자식 프레임의 unload 이전에 발생한다.

아래 예제를 확인해보자.

먼저, parent frame의 html을 만들어보았다.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Parent Frame</title>
  <style>
    h1{
      color:gray;
      border-bottom: 1px solid lightgray;
      padding-bottom: 20px;
    }
  </style>
</head>
<body>
  <h1>unload Event</h1>
  <iframe id="iframeChild" src="child-frame.html" frameborder="0"></iframe>
  <button id="btnRemoveChild">Remove child-frame.html</button>
  <script>
    const btnRemoveChild = document.getElementById("btnRemoveChild");
    btnRemoveChild.addEventListener("click",()=>{
      const iframeChild = document.getElementById("iframeChild");
      iframeChild.src = "";
    })
  </script>
</body>
</html>

iframe 태그를 이용해 child frame html을 받아오는 구조이다.

child html을 확인해보자.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Child Frame</title>
  <style>
    h2{
      color : darkgray;
    }
  </style>
</head>
<body>
  <h2>I'm the child!</h2>

  <script>
    window.addEventListener("unload",()=>{
      parent.console.log("Child-html is gone!");
    })
  </script>
</body>
</html>

전체 코드를 알아보자면 parent html 안에 child html을 iframe을 이용해 넣었고 'btnRemoveChild' 라는 버튼을 클릭하면 child html이 사라지게 되면서 child html의 스크립트 태그안의 unload 이벤트가 실행되게 되는 구조이다.


결과를 보시다시피 btnRemoveChild 버튼을 클릭함과 동시에 child html 이 사라지면서 unload 이벤트에 의해 console 창에 문구가 찍힘을 알 수 있다.

마무리...

이번 포스팅을 통해 HTML의 생명 주기와 Javascript의 몇가지 window 이벤트가 어떻게 그 생명주기에 관여를 하게 되는가에 대해서 알아보았다. 사실 이번 포스팅은 그냥 단순히 DOMContentLoaded , load , beforeunload , unload 이 4가지 이벤트에 대해 의미와 쓰임 정도에 대해서만 알아보았다. 사실 작성자 본인도 지금 이 시점에선 Javascript 초입자이고 세세히 파헤치지는 못한점이 아쉽다.
실제 프로젝트에서 이러한 이벤트들이 어떻게 쓰이는지를 알고 싶은 마음이 가장 크다.

무엇보다 깊게 들어가고자 하면 브라우저 엔진, 자바스크립트 엔진, HTML 파싱이 무언지, 렌더링과정은 어떻게 진행되는지 등등 , 이러한 브라우징 개념에 있어 선행학습이 필요하다 본다. 다음번에 웹 브라우저 최적화 시리즈를 따로 만둘어 다뤄보고자 한다. 빠른 시일내에 만들어 보겠다.

좋은 웹페이지 즐겨찾기