[카테고리:] 미분류

  • 웹 개발자를 위한 HTML과 JavaScript 실행 순서 이해하기

    웹 개발을 시작하는 초보자들이 자주 부딪히는 문제 중 하나는 “왜 내 JavaScript 코드가 제대로 작동하지 않을까?”라는 의문입니다. 이 문제의 핵심은 대부분 HTML 파싱과 JavaScript 실행 순서에 대한 이해 부족에서 비롯됩니다. 이 글에서는 웹 페이지가 로드될 때 실제로 어떤 일이 일어나는지, 그리고 초보 웹 개발자가 알아야 할 중요한 개념들을 살펴보겠습니다.

    DOM이란 무엇인가?

    DOM(Document Object Model)은 HTML 문서의 구조를 트리 형태로 표현한 것으로, 웹 페이지의 모든 요소를 객체로 다룰 수 있게 해줍니다. 브라우저가 HTML 문서를 로드하면, 파서(Parser)는 HTML을 파싱하여 DOM 트리를 구성합니다.

    중요한 점은 DOM이 리액트(React)와 같은 프레임워크에만 있는 개념이 아니라는 것입니다. DOM은 모든 웹 브라우저의 기본 기능이며, HTML5를 포함한 모든 웹 기술의 근간입니다.

    JavaScript 실행 순서와 <script> 태그 위치

    HTML 문서에서 <script> 태그의 위치는 JavaScript 코드의 실행 시점에 직접적인 영향을 미칩니다. 초보 개발자들이 자주 마주치는 문제 중 하나는 다음과 같은 코드입니다:

    <!DOCTYPE html>
    <html>
    <head>
        <script>
            // 아직 DOM이 로드되지 않은 상태
            const button = document.getElementById('myButton'); // null 반환
            button.addEventListener('click', function() {
                alert('버튼이 클릭되었습니다!');
            }); // TypeError: Cannot read property 'addEventListener' of null
        </script>
    </head>
    <body>
        <button id="myButton">클릭하세요</button>
    </body>
    </html>
    

    이 코드는 에러를 발생시킵니다. 왜냐하면 JavaScript가 실행될 때 <button> 요소가 아직 DOM에 존재하지 않기 때문입니다. 브라우저는 HTML을 위에서 아래로 순차적으로 파싱하며 실행하기 때문입니다.

    문제 해결 방법: 3가지 접근법

    1. <script> 태그 위치 변경

    가장 간단한 해결책은 <script> 태그를 </body> 닫는 태그 바로 앞에 배치하는 것입니다:

    <!DOCTYPE html>
    <html>
    <head>
        <!-- 여기에 CSS와 메타데이터 -->
    </head>
    <body>
        <button id="myButton">클릭하세요</button>
        
        <script>
            // 이 시점에서는 DOM이 모두 로드된 상태
            const button = document.getElementById('myButton'); // 정상 작동
            button.addEventListener('click', function() {
                alert('버튼이 클릭되었습니다!');
            });
        </script>
    </body>
    </html>
    

    2. DOMContentLoaded 이벤트 리스너 사용

    <script> 태그를 <head>에 유지하고 싶다면, DOMContentLoaded 이벤트를 사용하여 DOM이 완전히 로드된 후 코드를 실행할 수 있습니다:

    <!DOCTYPE html>
    <html>
    <head>
        <script>
            document.addEventListener('DOMContentLoaded', function() {
                // DOM이 완전히 로드된 후 실행
                const button = document.getElementById('myButton'); // 정상 작동
                button.addEventListener('click', function() {
                    alert('버튼이 클릭되었습니다!');
                });
            });
        </script>
    </head>
    <body>
        <button id="myButton">클릭하세요</button>
    </body>
    </html>
    

    3. defer 또는 async 속성 사용

    외부 JavaScript 파일을 사용할 때는 defer 또는 async 속성을 사용할 수 있습니다:

    <!DOCTYPE html>
    <html>
    <head>
        <!-- defer: HTML 파싱이 끝난 후 순서대로 실행 -->
        <script src="script.js" defer></script>
        
        <!-- async: HTML 파싱과 병렬로 로드, 로드 완료 즉시 실행 (순서 보장 없음) -->
        <script src="analytics.js" async></script>
    </head>
    <body>
        <button id="myButton">클릭하세요</button>
    </body>
    </html>
    
    • defer: HTML 파싱이 완료된 후, 스크립트가 선언된 순서대로 실행됩니다.
    • async: HTML 파싱과 병렬로 스크립트를 로드하고, 로드가 완료되면 즉시 실행합니다 (실행 순서 보장 없음).

    DOM 이벤트와 생명주기 이해하기

    웹 페이지의 로딩 과정에서 발생하는 주요 이벤트들은 다음과 같습니다:

    1. DOMContentLoaded: HTML이 완전히 로드되고 파싱되었을 때 발생하며, 외부 리소스(이미지, 스타일시트 등)는 기다리지 않습니다.
    2. load: 페이지의 모든 리소스(이미지, 스타일시트, 스크립트 등)가 로드되었을 때 발생합니다.
    // DOM만 로드되었을 때
    document.addEventListener('DOMContentLoaded', function() {
        console.log('DOM이 로드되었습니다!');
    });
    
    // 페이지의 모든 리소스가 로드되었을 때
    window.addEventListener('load', function() {
        console.log('페이지가 완전히 로드되었습니다!');
    });
    

    초보 웹 개발자가 알아야 할 추가 팁

    1. 브라우저 콘솔 활용하기

    JavaScript 디버깅의 첫 단계는 브라우저의 개발자 도구를 여는 것입니다. F12 키를 누르거나 마우스 우클릭 후 “검사”를 선택하면 콘솔을 볼 수 있습니다. 여기서 에러 메시지를 확인하고 console.log()를 통해 변수 값을 출력할 수 있습니다.

    2. 이벤트 버블링과 캡처링 이해하기

    DOM에서 이벤트는 기본적으로 버블링(bubbling)됩니다. 즉, 하위 요소에서 발생한 이벤트는 상위 요소로 전파됩니다. 이를 이해하면 이벤트 위임(Event Delegation)과 같은 고급 기법을 활용할 수 있습니다.

    // 부모 요소에 이벤트 리스너를 한 번만 추가하여 여러 자식 요소를 처리
    document.getElementById('parent').addEventListener('click', function(e) {
        if (e.target.className === 'child') {
            console.log('자식 요소가 클릭되었습니다!');
        }
    });
    

    3. 비동기 프로그래밍 기초 익히기

    현대 웹 개발에서는 비동기 프로그래밍이 필수적입니다. Promises, async/await 문법을 이해하면 API 호출이나 사용자 상호작용을 더 효과적으로 처리할 수 있습니다.

    // Promise 사용 예제
    fetch('https://api.example.com/data')
        .then(response => response.json())
        .then(data => console.log(data))
        .catch(error => console.error('에러 발생:', error));
    
    // async/await 사용 예제
    async function fetchData() {
        try {
            const response = await fetch('https://api.example.com/data');
            const data = await response.json();
            console.log(data);
        } catch (error) {
            console.error('에러 발생:', error);
        }
    }
    

    4. 반응형 디자인 기본 원칙 알기

    모바일 기기 사용이 증가함에 따라, 반응형 웹 디자인의 중요성도 커지고 있습니다. 미디어 쿼리를 사용한 기본적인 반응형 레이아웃을 구현할 줄 알아야 합니다.

    /* 기본 스타일 (모바일) */
    .container {
        width: 100%;
    }
    
    /* 태블릿 */
    @media (min-width: 768px) {
        .container {
            width: 750px;
        }
    }
    
    /* 데스크탑 */
    @media (min-width: 1024px) {
        .container {
            width: 970px;
        }
    }
    

    5. 웹 성능 최적화 기본 이해하기

    사용자 경험을 향상시키기 위해 웹 성능 최적화의 기본을 알아두면 좋습니다. 이미지 최적화, 불필요한 리소스 최소화, 브라우저 캐싱 활용 등이 여기에 포함됩니다.

    <!-- 이미지 지연 로딩 예제 -->
    <img src="placeholder.jpg" data-src="actual-image.jpg" class="lazy-load">
    
    <script>
        // 간단한 지연 로딩 구현
        document.addEventListener('DOMContentLoaded', function() {
            const lazyImages = document.querySelectorAll('.lazy-load');
            
            const lazyLoad = function() {
                lazyImages.forEach(img => {
                    if (isInViewport(img)) {
                        img.src = img.dataset.src;
                    }
                });
            };
            
            // 스크롤 시 이미지 로드
            window.addEventListener('scroll', lazyLoad);
            window.addEventListener('resize', lazyLoad);
            
            // 초기 로드
            lazyLoad();
        });
        
        function isInViewport(element) {
            const rect = element.getBoundingClientRect();
            return (
                rect.top >= 0 &&
                rect.left >= 0 &&
                rect.bottom <= window.innerHeight &&
                rect.right <= window.innerWidth
            );
        }
    </script>
    

    결론

    웹 개발을 시작하는 과정에서 HTML 파싱과 JavaScript 실행 순서를 이해하는 것은 매우 중요합니다. DOM이 어떻게 구성되고, JavaScript가 언제 실행되는지 알면 많은 일반적인 버그를 예방할 수 있습니다.

    이 글에서 다룬 <script> 태그 위치 조정, DOMContentLoaded 이벤트 사용, defer/async 속성 활용은 웹 개발자로서 꼭 알아야 할 기본 지식입니다. 이러한 기초를 탄탄히 다진 후, 더 복잡한 프레임워크와 라이브러리로 발전해 나가는 것이 좋습니다.

    계속 학습하고, 실험하고, 코드를 작성하며 경험을 쌓아가세요. 웹 개발의 여정은 끝이 없지만, 그만큼 보람차고 흥미로운 여정이 될 것입니다!