코딩/Javascript

[JS-중급편-이벤트] 4. 코드 실행의 최적시간

드리프트 2021. 3. 6. 19:39
728x170

 

 

자바스크립트를 작성하다 보면 어떻게 하면 코드가 원하는 시간에 딱 맞게 실행되도록 하는 게 중요합니다.

 

페이지 하단에 코드를 배치하고 나서 페이지가 로드되면 모든 게 끝이 아닙니다.

 

때로는 페이지가 로드되기 전에 특정 코드가 실행되지 않도록 해야 될 때도 있고,

 

때로는 페이지 상단에 코드를 넣는 경우도 있습니다.

 

 

 

페이지 로드 중 발생하는 문제

 

 

링크를 클릭하거나 또는 브라우저에서 URL을 입력한 후 Enter 키를 누르면  이 때 페이지가 로드됩니다.

 

보통은 페이지 완료까지 걸리는 시간은 매우 짧습니다.

 

 

페이지 로드와 페이지 로드 사이의 짧은 시간 동안 우리가 모르는 컴퓨터 관련 작업이 많이 발생합니다.

 

여기서 중요하게 봐야할 점은 HTML 페이지에 있는 모든 자바스크립트 코드가 실행된다는 것입니다.

 

코드가 정확히 실행완료되는 시점은 페이지가 로드되는 동안 다음 경우의 조합에 따라 달라집니다.

 

  • DOMContentLoaded 이벤트
  • Load 이벤트
  • 스크립트 요소의 비동기 속성
  • 스크립트 요소의 지연 속성
  • DOM에서 스크립트가 있는 위치

 

일단 위의 리스트는 뭔가 어렵게 느껴질 겁니다.

 

일단, 쉽게 이해하기 위해 페이지 로드의 세 단계를 살펴보겠습니다.

 

 

1단계

 

첫 번째 단계는 브라우저가 새 페이지를 로드하기 시작할 때입니다.

여기서는 이해하기에 특별히 어려운 점은 없습니다.

 

페이지를 로드하라는 요청이 브라우저에게 전달되었지만 아직은 특별히 특정 항목이 다운로드되지 않은 상태입니다.

 

 

2단계

 

페이지의 Raw HTML 소스와 HTML에 있는 DOM이 로드되고 파싱 되는 두 번째 단계에서는 좀 더 복잡한 일이 벌어집니다.

 

여기서 주목할 점은 이미지와 스타일 시트와 같은 외부 리소스가 아직은 로드 되지 않았다는 겁니다.

 

HTML 소스에 지정된 순수 HTML 코드 즉, 원시 콘텐츠만 볼 수 있습니다.

 

 

3단계

 

마지막 단계는 이미지, 스타일 시트, 스크립트 및 기타 외부 리소스가 완전히 로드되어 표시되는 단계입니다.

 

브라우저가 로딩 중이라는 모래시계 애니메이션이 없어지는 단계입니다.

 

 

 

이제 콘텐츠를 로드할 때 거치는 세 단계에 대한 기본적인 개념을 훑어 보았습니다. 다음 단계로 넘어가 보겠습니다.

 

 

 

DOMContentLoaded 및 Load 이벤트

 

페이지가 로드되는 동안 두 가지 자바스크립트 이벤트가 발생 합니다.

 

DOMContentLoaded 이벤트는 페이지의 DOM이 완전히 파싱 되는 2단계가 끝날 때 발생합니다.

 

Load 이벤트는 페이지가 완전히 로드되는 3단계 끝에서 시작됩니다.

 

DOMContentLoaded, Load 이벤트를 사용하여 프로그래머는 코드를 실행시키려고 하는 정확한 지점을 정할 수 있습니다.

 

다음 예를 볼까요?

document.addEventListener("DOMContentLoaded", theDomHasLoaded, false);
window.addEventListener("load", pageFullyLoaded, false);

function theDomHasLoaded(e) {
    // do something
}

function pageFullyLoaded(e) {
    // do something again
}

 

여기서 유의해야 될 점은 DOMContentLoaded 이벤트는 document 요소에서 listen 해야 되고, Load 이벤트는 window 요소에서 listen 해야 된다는 겁니다.

 

위의 두 이벤트가 중요한 이유는 무엇인지 알아볼까요?

 

querySelector 또는 querySelectorAll 함수와 같이 DOM 작업에 전적으로 의존하는 코드가 있는 경우 DOM이 완전히 로드된 후에만 ​​코드가 실행되도록 해야 합니다.

 

DOM이 완전히 로드되기 전에 액세스 하려고 하면 불완전한 결과가 표시되거나 결과가 전혀 표시되지 않을 수 있습니다.

 

DOM이 준비되기 전에 코드가 실행되는 상황이 발생하지 않도록 하기 위해서는 DOMContentLoaded 이벤트를 수신하고 해당 이벤트가 발생되었을 때에만 ​​DOM에 의존하는 코드를 실행하는 겁니다.

 

document.addEventListener("DOMContentLoaded", theDomHasLoaded, false);

function theDomHasLoaded(e) {
    var images = document.querySelectorAll("img");

    // do something with the images
}

반대로 페이지가 완전히 로드된 후에만 ​​코드를 실행하려는 경우 load 이벤트를 사용하면 됩니다.

 

 

DOM에서의 스크립트 위치

 

앞전에, 우리는 HTML 소스에 자바스크립트를 작성하는 다양한 방법을 살펴보았습니다.

 

DOM에서 자바스크립트 요소의 위치가 실제 실행 타이밍에 영향을 주는 것을 보았습니다.

 

아래는 HTML 소스 어딘가에 인라인으로 삽입된 일부 코드 일 수 있습니다.

<script>
var number = Math.random() * 100;
alert("A random number is: " + number);
</script>

 

아래는 외부 파일 형태로 저장되었고 이를 HTML 소스에서 참조하는 방식입니다.

<script src="/foo/something.js"></script>

 

 

 

브라우저는 DOM을 위에서 아래로 순차적으로 파싱 분석합니다. 

 

위에서 아래로 순차적으로 파싱 되면서 도중에 발견되는 스크립트 요소는 DOM에 나타나는 순서 그대로 파싱 분석됩니다.

 

다음은 여래 개의 스크립트 요소가 있는 매우 간단한 예입니다.

 

<!DOCTYPE html>
<html>
<body>
    <h1>Example</h1>
    <script>
        console.log("inline 1");
    </script>
    <script src="external1.js"></script>
    <script>
        console.log("inline 2");
    </script>
    <script src="external2.js"></script>
    <script>
        console.log("inline 3");
    </script>
</body>
</html>

자바스크립트가 인라인 형태인지 아니면 외부 파일을 참조하는 방식인지는 중요하지 않습니다. 

 

모든 자바스크립트는 동일하게 취급되며 HTML 소스에 나타나는 순서대로 실행됩니다. 

 

위의 예에서 보면  자바스크립트가 실행되는 순서는 inline 1, external1, inline 2, external2, inline 3입니다.

 

자, 여기에 정말 중요한 점이 있습니다. 

 

DOM이 위에서 아래로 파싱 되기 때문에 자바스크립트 요소는 이미 파싱 된 모든 DOM 요소에 액세스 할 수 있습니다. 

 

반면에, 자바스크립트는 아직 파싱 되지 않은 DOM 요소에는 액세스 할 수 없습니다.

 

그런데 여기서 뭔가 이상한 점이 있죠?

 

페이지 하단에 </body> 요소 바로 위에 자바스크립트가 있다고 가정해 보겠습니다.

 

<!DOCTYPE html>
<html>
<body>
    <h1>Example</h1>

    <p>
        Quisque faucibus, quam sollicitudin pulvinar dignissim, nunc velit
        sodales leo, vel vehicula odio lectus vitae mauris. Sed sed magna
        augue. Vestibulum tristique cursus orci, accumsan posuere nunc
        congue sed. Ut pretium sit amet eros non consectetur. Quisque
        tincidunt eleifend justo, quis molestie tellus venenatis non.
        Vivamus interdum urna ut augue rhoncus, eu scelerisque orci
        dignissim. In commodo purus id purus tempus commodo.
    </p>

    <button>Click Me</button>

    <script src="something.js"></script>
</body>
</html>

 

something.js가 실행되면 h1, p, button 요소와 같이 바로 위에 있는 모든 DOM 요소에 액세스 할 수 있습니다.

 

반면, 자바스크립트 요소가 문서의 맨 처음에 있으면 그 아래에 있는 DOM 요소에 대한 정보가 없습니다.

 

<!DOCTYPE html>
<html>
<body>
    <script src="something.js"></script>

    <h1>Example</h1>

    <p>
        Quisque faucibus, quam sollicitudin pulvinar dignissim, nunc velit
        sodales leo, vel vehicula odio lectus vitae mauris. Sed sed magna
        augue. Vestibulum tristique cursus orci, accumsan posuere nunc
        congue sed. Ut pretium sit amet eros non consectetur. Quisque
        tincidunt eleifend justo, quis molestie tellus venenatis non.
        Vivamus interdum urna ut augue rhoncus, eu scelerisque orci
        dignissim. In commodo purus id purus tempus commodo.
    </p>

    <button>Click Me</button>

</body>
</html>

 

페이지 하단에 자바스크립트 요소를 배치하면 DOMContentLoaded 이벤트를 사용하여 작성한 것 같은 효과를 얻을 수 있습니다.

 

즉, 모든 DOM 요소 다음에, 다시 말해 HTML 소스의 끝 부분에 자바스크립트를 위치시킨다면 DOMContentLoaded 접근 방식을 수행하지 않아도 됩니다.

 

만약, 실제로 DOM의 맨 위에 자바스크립트 요소를 배치하려고 한다면 항상 DOMContentLoaded 이벤트가 발생한 후 DOM에 의존하는 코드가 제대로 실행되는지 확인해야 합니다.

 

그러나, 여기에는 문제가 있습니다. 

 

일반적으로 프로그래머라면 DOM의 맨 아래에 자바스크립트 요소를 배치하는 것을 좋아합니다. 

 

HTML 소스의 맨 아래에 위치해서 좀 더 손쉽게 DOM을 액세스 할 수 있는 것 외에 자바스크립트가 페이지 하단에 배치되는 것이 좋은 이유는 더 있습니다. 

 

자바스크립트 요소가 파싱 되어 분석될 때 브라우저는 자바스크립트 코드가 실행되는 동안 페이지의 다른 항목이 실행되는 것을 잠시 중지합니다. 

 

만약 오래 시간 동안 실행되는 자바스크립트가 있거나 또는 외부 자바스크립트 소스를 다운로드하는 데 시간이 많이 걸린다면 이럴 때는 HTML 페이지가 정지된 것처럼 보이게 됩니다. 

 

이 시점에서 DOM이 부분적으로만 파싱 된 경우 페이지가 깨지거나 브라우저가 응답 없음이 된 것처럼 불안해 보입니다.

 

 

스크립트 요소, 지연(Defer) 및 비동기(Async)

 

외부 자바스크립트를 가리키는 요소는 defer 및 async 속성을 설정할 수 있습니다.

 

<script async src="myScript.js"></script>
<script defer src="somethingSomethingDarkSide.js"></script>

defer와 async 속성은 DOM에서 표시되는 위치와 관계없이 자바스크립트가 실행될 때 변경되므로 좀 더 자세하게 스크립트가 어떻게 변경되는지 살펴보겠습니다.

 

 

비동기(Async)

 

async 속성을 사용하면 자바스크립트를 비동기적으로 실행할 수 있습니다.

<script async src="someRandomScript.js"></script>

 

이 글의 앞부분에서 잠깐 언급했듯이 자바스크립트가 파싱 실행될 때 브라우저가 다른 작업을 중지하고 브라우저 리소스를 사용하지 못하도록 차단할 수 있습니다. 

 

자바스크립트 요소에 비동기(async) 속성을 설정하면 이 문제를 완전히 피할 수 있습니다.

 

자바스크립트는 브라우저가 판단할 때 실행 가능할 때마다 실행되지만, 나머지 HTML 소스를 브라우저가 실행하는 것을 완전히 차단하지는 않습니다.

 

코드를 실행할 때 이처럼 비동기(async)로 표시된 자바스크립트가 항상 순서대로 실행되는 것은 아닙니다.

 

비동기(async)로 표시된 여러 스크립트가 HTML 소스에 지정된 순서와 다르게 실행되는 경우가 있을 수 있습니다.

 

확실하게 보장할 수 있는 점은 비동기(async)로 표시된 스크립트가 Load 이벤트가 시작되기 전에 실행되기 시작한다는 것입니다.

 

 

지연(Defer)

 

defer 속성은 async와는 약간 다릅니다.

<script defer src="someRandomScript.js"></script>

 

defer로 표시된 스크립트는 정의된 순서대로 실행되지만 DOMContentLoaded 이벤트가 시작되기 전에 마지막으로 실행됩니다. 

 

다음 예를 볼까요?

 

<!DOCTYPE html>
<html>
<body>
    <h1>Example</h1>
    <script defer src="external3.js"></script>
    <script>
        console.log("inline 1");
    </script>
    <script src="external1.js"></script>
    <script>
        console.log("inline 2");
    </script>
    <script defer src="external2.js"></script>
    <script>
        console.log("inline 3");
    </script>
</body>
</html>

 

위 코드의 실행 결과를 상상해 보십시오.

 

어쨌든 스크립트는 inline 1, external1, inline 2, inline 3, external3  및 external2의 순서로 실행됩니다.

 

external3 및 external2 스크립트는 지연(defer)으로 표시되어 있으므로 HTML 소스의 다른 위치에서 선언되었음에도 불구하고 HTML 소스의 끝부분에서 실행됩니다.

 

 

결론

 

 

다음 다이어그램을 참고해 보면 지금까지 배운 것을 이해하는데 조금 도움이 될 겁니다.

그렇다면 자바스크립트를 로드하기에 가장 적절한 시기는 언제일까요?

 

정답은...

 

  • </body> 요소 바로 위에 있는 DOM 아래에 스크립트를 배치합니다.
  • 다른 사람이 사용할 라이브러리를 만들지 않는다면, DOMContentLoaded 또는 Load 이벤트를 수신하여 코드를 복잡하게 만들지 마십시오. 대신 </body> 요소 바로 위에 있는 DOM 요소 바로 아래에 스크립트를 배치하십시오.
  • defer 속성을 사용하여 외부 파일을 읽어 들이십시오.
  • HTML 소스에 있는 DOM에 의존하지 않고 다른 스크립트와 연관된 일을 하는 자바스크립트는 HTML 소스 상단에 비동기 속성으로 배치하십시오.

이 네 단계를 잘 이해한다면 자바스크립트 코드가 적시에 실행되도록 할 수 있을 겁니다.

 

 

그리드형