본문으로 바로가기

이벤트 위임

category JavaScript 2020. 10. 12. 01:54

앞전 우리는 하나의 박스에 클릭 이벤트를 등록했었다

3개의 박스에 모두 이벤트를 등록하려면 어떻게 해야할까?

아주 간단하다 3개의 박스에 각각 클릭 이벤트를 등록해주면 된다

(function () {
    const boxes = document.querySelectorAll('.test');

    const clickHandler = function(){
        this.classList.add('apply');
    }

    for(let i=0; i<boxes.length;i++){
        boxes[i].addEventListener('click',clickHandler);
    }
})();

3개의 박스는 모두 test클래스를 가지고 있음으로 querySelectorAll을 이용해서 3개의 박스를 모두 가져와서 boxes에 담았다

그리고 반복문을 돌리면서 하나하나씩 이벤트를 등록하고 있는 코드인데

이전과 다른점이 하나 있다면 clickHandler에 this가 들어가 있다는 점

반복문에서 호출되는 addEventListener는 각각의 박스에서 호출하는 하는 메소드이기 때문에 this는 각각의 박스에 해당한다

이렇게 간단하고 쉬운 코드는 단점을 가지고 있다

같은 이벤트를 다수 등록하면 성능에 저하가 발생한다는 것인데

이러한 단점을 없애기 위해서 이벤트 위임이라는 기술을 사용한다

이벤트 위임을 배우기 전에 이벤트 핸들러에 대해서 조금 더 알아야할 부분이있다


addEventListener가 호출될때 받는 첫인자는 발생할 이벤트, 두번째 인자로 받는 실행할 함수를 우리는 콜백 함수, 이벤트 핸들러라고 부른다

이 이벤트 핸들러는 첫번째 인자로 이벤트 객체를 받아오는데 이는 명시하지 않아도 가져온다

(function () {
    const cont = document.querySelector('.container');

    const clickHandler = function(){
        console.log('event');
        console.log(event);
        console.log('event.target');
        console.log(event.target);
        console.log('event.currentTarget');
        console.log(event.currentTarget);
    }

    cont.addEventListener('click',clickHandler);
})();

3개의 박스를 가진 컨테이너를 가져와 클릭 이벤트를 부여했다

마우스 클릭 지점

이처럼 인자로 받아오지 않고 이벤트 객체를 사용해도 에러가 나지않지만 보통 e나 event로 인자를 명시해주는 것이 바람직하다

이제 클릭하는 부위에 따라서 출력되는 콘솔 코드를 보고 어떤 코드가 어떤 것을 가르키는지 알아볼 것이다

번호 순서대로 클릭하면 어떻게 출력되는지 알아보자

1번

2번

3번

알아볼 수 있겠는가?

클릭 이벤트에서의 event는 마우스를 클릭한 위치의 좌표를

event.target은 마우스를 클릭한 위치에 있는 개체를

event.currentTarget은 this를 가르키고 있다


이제 다시 이벤트 위임에 대해서 알아보자

이벤트 위임은 내가 자식들에게 하나하나 이벤트를 등록하지 않고

그들의 부모에게만 이벤트를 등록한 뒤 '니 자식들한테는 니가 등록해' 하고 일을 위임하는 방법이라고 생각하면 쉽다

바로 위에서 알아본 클릭 이벤트 핸들러의 이벤트 객체를 이용하게된다

(function () {
    const cont = document.querySelector('.container');

    const clickHandler = function(e){
        e.target.classList.add('apply');
    }

    cont.addEventListener('click',clickHandler);
})();

위에서 알아보았던 이벤트 객체의 target을 이용해서

클릭한 부분의 객체 정보를 받아와서 클래스리스트에 apply를 추가해주는 코드다

이벤트는 하나만 주었지만 매우 잘 작동하는 것을 볼 수 있다

바깥의 박스인 컨테이너를 클릭해보자

예상하지 못한 결과다

내가 원하는 건 검정 박스에만 적용되길 바랐는데 컨테이도 클릭 이벤트가 부여되어버렸다

(function () {
    const cont = document.querySelector('.container');

    const clickHandler = function(e){
        if(e.target.classList.contains('test'))
            e.target.classList.add('apply');
    }

    cont.addEventListener('click',clickHandler);
})();

우리가 원하는 것은 테스트 박스에만 이벤트가 적용되는 것이기 때문에

클릭한 객체가 test클래스를 포함하고 있을 때만 apply 클래스를 추가하게끔 조건문을 주었다

더이상 컨테이너 박스를 클릭해도 빨간색 테두리가 생기지 않는 것을 볼 수 있다


살짝 다른 예제를 보자

<div class="container">
    <button class="test a">
        <span>AA</span>
    </button>
    <button class="test b">
        <span>BB</span> 
    </button>
    <button class="test c">
        <span>CC</span>
    </button>
</div>

전과 거의 비슷하지만 이번엔 test a,b,c 버튼이 3개있고 버튼 안에는 span태그로 버튼의 이름을 나타내고 있다

자바스크립트 코드는 위와 똑같다

버튼의 여백을 누르면 정상적으로 작동하는데

버튼의 이름을 정확하게 누르면 테두리가 생기지 않는다

span태그의 범위

if(e.target.classList.contains('test'))

그림의 파란 부분은 클릭해도 바로 위의 코드 때문에 apply가 추가되지 않는다

AA,BB,CC를 담고있는 span태그는 test클래스를 가지고 있지 않기 때문

    const clickHandler = function(e){
        let temp = e.target;

        while(!temp.classList.contains('test')){
            temp = temp.parentNode;
        }

        temp.classList.toggle('apply');
    }

위와 같이 클릭 핸들러를 손봐주면 되는데

내가 클릭한 객체를 임시 변수 temp에 담은 뒤

temp가 test클래스를 가진 박스임을 확인한다

test클래스를 가지지 않았다면? temp에 내가 클릭한 객체의 부모를 담은 뒤 다시 확인.. test클래스를 가진 박스를 만날 때까지 반복한다

이 코드를 실행한 뒤 AA,BB,CC를 정확히 클릭하면 아래와 같이 진행된다

e.target은 span 노드를 가르키고 있기 때문에 temp에는 span이 담긴다
span노드는 test클래스를 가지지 않았기 때문에 while안의 명령문을 실행
temp에 span노드의 상위인 button노드가 다시 담긴다
button가 test클래스를 가지고 있는지 확인
반복문 종료

이제는 AA,BB,CC를 정확히 클릭해도 테두리가 생성됨을 확인할 수 있다

사실 이 코드에도 문제가 한가지 존재하고 있다

우리가 원하는 클래스를 가진 노드의 하위 노드를 클릭했다면 문제 없이 상위 노드를 찾아서 반복문이 종료되어 정상 작동을 하지만

하위 노드가 아니라면 어떻게 될까?

DOM트리를 벗어나면서 에러가 난다

    const clickHandler = function(e){
        let temp = e.target;

        while(!temp.classList.contains('test')){
            temp = temp.parentNode;
            if(temp.nodeName==='BODY')return;
        }

        temp.classList.toggle('apply');
    }

현재 temp의 노드 이름이 BODY인지 확인하는 조건문을 반복문 안에 넣었다

이 코드가 추가되면 상위 노드를 찾아가다가 temp가 BODY일 때 함수가 클릭 핸들러가 종료되어 이벤트가 발생하지 않게된다

'JavaScript' 카테고리의 다른 글

공 움직이기  (0) 2020.10.13
scroll event  (0) 2020.10.12
전역 변수 줄이기  (0) 2020.10.11
이벤트  (0) 2020.10.10
DOM 조작  (0) 2020.10.08