3 분 소요

이벤트를 작성하던 도중 순차적으로 잘 실행되어야만 할 것 같던 스크립트 문에서 자꾸 오류가 나서 그 이유를 찾아보았다.

📝 HTML 실행 위치에 따른 오작동

보통 head 태그 안에 script를 작성하는데, 스크립트 문을 html의 밑에 작성해야 할 수도 있다. 그렇지 않으면 html 요소가 생기기도 전에 스크립트가 동작하여 다음과 같은 에러가 발생할 수 있다.

<script>
    var targetBtn = document.getElementById('btn');
    targetBtn.onclick = function(){
        alert('Hello world');
        // 실행 >> Hello world
    }
</script>
.
.
.
<button id='btn'>button</button>

onloadErr.png

스크립트를 head 태그 안에 적어주고 싶다면 다음과 같이 해결할 수 있다.

<script>
	window.onload = function(){
    var targetBtn = document.getElementById('btn');
    targetBtn.onclick = function(){
        alert('Hello world');
        // 실행 >> Hello world
    }
	}
</script>
.
.
.
<button id="btn">button</button>

window.onload 메서드를 통해서 페이지가 다 로드된 이후 스크립트가 동작한다. 하지만 이 방식도 문제가 생길 수 있는데, window.onload() 메서드는 다는 한 번만 정의할 수 있으므로 addEventListener를 통해 이벤트를 지정하면 중복 사용이 가능하다.

<script>
	window.addEventListener('load', function(){
    var targetBtn = document.getElementById('btn');
    targetBtn.onclick = function(){
        alert('Hello world');
        // 실행 >> Hello world
    }
	}, false);
</script>
.
.
.
<button id="btn">button</button>

📖 이벤트 버블링과 캡처링

어떤 html 요소와 상호작용을 했을 때, 이벤트 핸들러가 작동하는 방식을 알아보고 있는데, html 요소들이 중첩되어 있다면 이벤트가 어떻게 동작하게 되는지 알아보자.

<style>
    #grand{border:1px solid red;}
    #parent{border:1px solid green;}
    #child{border:1px solid blue;}
</style>
<script>
		function log(msg){
		    const debug = document.getElementById("debugConsole");
		    if(debug !== null){
		        debug.innerHTML += msg + "<br>";
		    }
		}
<script>
<body>
    <div id="grand">
        grand의 상단
        <div id="parent">
            parent의 상단
            <div id="child">
                child
            </div>
            parent의 하단
        </div>
         grand의 하단
    </div>
    <br><br>
    <div id="debugConsole" style="border:1px solid red"></div>
</body>

html 내용이고, debugConsole이라는 div를 콘솔창 느낌으로 볼 수 있게 구성했다. 화면은 이렇게 간단하게 구성해보았다.

page.png

<script>
    window.onload = function(){
        const child = document.getElementById('child');
        const parent = document.getElementById('parent');
        const grand = document.getElementById('grand');

        grand.addEventListener("mousedown", ev => {
            log("Grand");
        }, false);
        parent.addEventListener("mousedown", ev => {
            log("Parent");
        }, false);
        child.addEventListener("mousedown", ev => {
            log("Child");
        }, false);
    }
</script>

각 블록을 클릭하면 속해 있는 div의 아이디 값을 출력하도록 스크립트문을 작성했다.

child 블록을 클릭했을 때 결과

child1.png

parent 블록을 클릭했을 때 결과

parent1.png

보면 알 수 있듯이, 클릭을 한 자기 자신만 호출되는 게 아니라, 상위 요소까지 같이 출력되는 것을 볼 수 있다. 그리고 자식 요소부터 불리고 부모 요소가 불리는데 이것이 버블링의 형태이다.

📖 버블링

이벤트가 발생한 요소부터 부모 요소를 거슬러 올라가 window까지 걸려있는 모든 이벤트가 전부 실행되는 것을 버블링이라고 한다.

이번엔 캡처링을 알아보자. addEventListener의 마지막 인자를 true로 바꿔서 캡처링을 하겠다고 해보자.

<script>
    window.onload = function(){
        const child = document.getElementById('child');
        const parent = document.getElementById('parent');
        const grand = document.getElementById('grand');

        grand.addEventListener("mousedown", ev => {
            log("Grand");
        }, true);
        parent.addEventListener("mousedown", ev => {
            log("Parent");
        }, true);
        child.addEventListener("mousedown", ev => {
            log("Child");
        }, true);
    }
</script>

child 블록을 클릭했을 때 결과

child2.png

parent 블록을 클릭했을 때 결과

parent2.png

이번에도 역시 자기 자신만 호출되는 것이 아니라 상위 요소까지 같이 출력되는 것을 볼 수 있다. 하지만 다른 점이 있는데, 이번엔 상위 요소가 먼저 출력이 되고, 하위 요소가 출력이 되는데, 이것이 캡처링의 형태이다.

📖 캡처링

버블링과 반대의 개념으로, window부터 이벤트가 발생한 요소까지 지정되어 있는 모든 이벤트가 실행된다.

그러면 이번엔 캡처링과 버블링을 섞어보겠다.

<script>
    window.onload = function(){
        const child = document.getElementById('child');
        const parent = document.getElementById('parent');
        const grand = document.getElementById('grand');

        grand.addEventListener("mousedown", ev => {
            log("Grand");
        }, true);
        parent.addEventListener("mousedown", ev => {
            log("Parent");
        }, false);
        child.addEventListener("mousedown", ev => {
            log("Child");
        }, false);
    }
</script>

grand 요소까지는 캡처링, parent 요소부터는 버블링이 적용된 모습이다. 이제 child 블록을 누르면 어떻게 될까? 딱봐도 grand에 지정된 이벤트가 실행이 되고, 그 후 버블링 적용되어 child, parent가 거꾸로 이벤트 실행이 될 것 같은데 실제로 그런지 확인해보자.

child 블록을 클릭했을 때 결과

child3.png

생각한 대로 동작이 되는 것을 확인할 수 있다.

📖 Event.stopPropagation()

버블링을 막는 방법으로 stopPropagation 메서드를 활용할 수 있다.

<script>
    window.onload = function(){
        const child = document.getElementById('child');
        const parent = document.getElementById('parent');
        const grand = document.getElementById('grand');

        grand.addEventListener("mousedown", ev => {
            log("Grand");
        }, false);
        parent.addEventListener("mousedown", ev => {
            log("Parent");
            ev.stopPropagation();
        }, false);
        child.addEventListener("mousedown", ev => {
            log("Child");
            ev.stopPropagation();
        }, false);
    }
</script>

child를 눌렀을 때 버블링이 되지 않게 해 child 본인의 메서드만 실행되도록 해당 함수를 스크립트에 추가해보았다.

child 블록을 클릭했을 때 결과

child4.png

이제 버블링이 발생하지 않는 것을 확인할 수 있다. 다만 무분별하게 사용할 경우 특정 부분의 이벤트가 동작 될 일이 없는 deadzone이 생길 수 있으므로 곡 필요할 때만 사용해야 한다.

📖 removeEventListener()

해당 함수를 통해 지정했던 이벤트들을 제거할 수 있다. 사용법이 동일해 따로 다루지는 않겠다.




참고


https://inpa.tistory.com/entry/JS-📚-windowonload-정리

https://jhyonhyon.tistory.com/24

댓글남기기