이벤트

이벤트 시스템은 상당히 복잡합니다.

BOM도 이벤트를 지원하지만 정의할 문서가 없었기 때문에 혼란스럽습니다.

이벤트는 상황에따라서 단순할 수도, 매우 복잡할 수도 있습니다.

우선 핵심 개념부터 알아봅시다.

이벤트 흐름

이벤트 흐름이란 페이지에 이벤트가 전달되는 순서를 설명합니다.

생각해보십시오.

화면의 버튼을 클릭하면 버튼도 클릭하고 버튼의 컨테이너도 클릭하는 것이고 페이지 전체도 클릭하는 것입니다.

이와같은 문제로 인해 개발자들은 이벤트 흐름을 생각하게 됐는데 IE는 이벤트 버블링을 지원했고 넷스케이프는 이벤트 캡쳐링을 지원했습니다.

이벤트 버블링

IE의 이벤트 흐름은 이벤트 버블링이라 부릅니다.
이벤트가 발생하면 트리위치강 가장 깊은곳부터 거슬러 올라가게 되고 이모습이 마치 거품이 올라가는것 같아서 이벤트 버블링이라고 불리웁니다.

<!DOCTYPE html>
<html>
    <head>
        <title>이벤트 버블링 예제</title>
    </head>
    <body>
        <div id="mydiv">Click Me</div>
    </body>
</html>

위 요소에서 #mydiv를 클릭 했다면 아래와 같이 이벤트가 발생합니다.

  1. div
  2. body
  3. html
  4. document

이벤트 캡처링

넷스케이프 커뮤니케이터 팀이 만든 이벤트 캡쳐링 이벤트 흐름입니다.
이벤트 버블링과 정반대로 최상위 노드에서 이벤트가 발생합니다.
이벤트 버블링 때 처럼 div요소를 클릭했을 때 아래 순서로 이벤트가 발생합니다.

  1. document
  2. html
  3. body
  4. div

이벤트 버블링을 주로 쓰며
오래된 브라우저에서는 이벤트 캡처링을 지원하지 않습니다.
특별한 상황에서만 쓰길 권장합니다.

DOM 이벤트 흐름

DOM이벤트 흐름은 3단계로 이루어져있습니다.

  1. 이벤트 캡처링 단계
  2. 타깃 단계
  3. 이벤트 버블링 단계

이벤트 버블링 떄 처럼 div요소를 클릭한다면 아래 순서로 이벤트가 발생합니다.

  1. document
  2. html
  3. body
  4. div
  5. body
  6. html
  7. document

1~3은 이벤트 캡처링 단계, 4는 타깃 단계, 5~7은 이벤트 버블링 단계입니다.


이벤트 핸들러

이벤트란? 사용자 또는 브라우저가 취하는 특정 동작입니다.
이벤트는 click, load, mouseover같은 이름이 있고 이벤트에 응답해서 호출되는 함수를 이벤트 핸들러(이벤트 리스너)라고 합니다.

이벤트 핸들러의 이름은 “on”으로 시작합니다.
즉 click의 이벤트 핸들러는 onclick이며 마찬가지로 load -> onload 입니다.

HTML 이벤트 핸들러

요소가 지원하는 이벤트는 이벤트 핸들러 이름과 HTML속성을 이용하여 사용할 수 있습니다.
속성 값은 실행할 자바스크립트의 코드입니다. 예를 봅시다.

<input type="button" value="Click" onClick="alert('Hello')" />

//위코드와 동일합니다.
<input type="button" value="Click" onClick="alert(&quot;Hello&quot;)" />

예제에 버튼을 클릭하면 alert창이 표시됩니다.

onclick속성 값에 할당한 자바스크립트 코드가 실행됨을 확인 할 수 있습니다.
onclick 값은 큰따옴표로 묶여있어서 내용에 자바스크립트는 작은따옴표를 사용했으며 그대로 쓰고 싶으면 Click2버튼처럼 해주면 됩니다.

HTML이벤트 핸들러는 아래와 같이 다른 곳에서 정의한 스크립트를 연결할 수도 있습니다.

<script type="text/javascript">
    function showMessage(){
        alert("hello!");
    }
</script>
<input type="button" value="Click" onClick="showMessage()" />

위예제는 Click을 누르면 showMessage() 함수가 실행됩니다.

이런 방식으로 할당한 이벤트 핸들러는 몇 가지 특징이 있으며 아래 소스와 함께 설명을 추가 했습니다.

//이벤트 핸들러는 attribute값을 감싸는 함수가 생성되며, event라는 특별한 로컬변수를 사용할 수 있습니다.
//click 출력
<input type="button" value="Click" onClick="alert(event.type)" />

//this값은 이벤트 타깃 요소와 일치합니다.
//Click2 출력
<input type="button" value="Click2" onClick="alert('Hello')" />

//attribute값을 감싸는 함수는 with를 통해서 생성됩니다.
function(){
    with(document){
        with(this){
            //속성값
        }
    }
}
//그렇기 떄문에 아래와 같이 바로 프로퍼티에 접근가능합니다.
//Click3 출력
<input type="button" value="Click3" onClick="alert(value)" />

//input의 경우는 attribute값을 감싸는 함수가 생성될 때 부모 폼요소도 포함되서 아래와 같이 동작합니다.
function(){
    with(document){
        with(this.form){
            with(this){
                //속성값
            }
        }
    }
}
//그렇기 때문에 이벤트핸들러 코드는 폼 요소를 거치지않고 형제 멤버에 접근이 가능합니다.
<form method="post">
    <input type="text" name="username" value="" />
    <input type="button" value="Echo Username" onClick="alert(username.value)" />
</form>
//형제 요소의 name인 username을 직접 참조하고 있습니다.

단점

  • 이벤트 핸들러 함수의 스코프 체인 확장 결과가 브라우저마다 다름.
  • 식별자 해석에 사용하는 규칙이 자바스크립트 엔진에 따라 미묘하게 다름.
  • 결과 비적격 객체 멤버에 접근할때 에러 발생위험
  • 이벤트 핸들러를 HTML에서 할당시 HTML과 자바스크립트가 단단하게 묶임.

위와 같은 단점으로 인하여 이벤트 핸들러를 HTML보다 자바스크립트에서 할당합니다.

DOM레벨 0 이벤트 핸들러

가장 전통적인 방법부터 알아보겠습니다.
window와 document를 포함 모든 요소는 이벤트 핸들러 프로퍼티가 있으며 일반적으로 소문자입니다.

var btn = document.getElementById("myBtn");
btn.onclick = function(){
    alert("Clicked");
};

DOM 레벨 0 방법으로 이벤트 핸들러를 할당한다면, 이벤트 핸들러는 해당 요소의 메서드로 간주됩니다.
this는 요소 자체입니다. 아래 예제를 봅시다.

var btn = document.getElementById("myBtn");
btn.onclick = function(){
    alert(this.id); // myBtn
};

this.id를 통해서 요소의 id를 가져왔습니다.

이벤트 제거는 아래와 같이 합니다.

btn.onclick = null; //핸들링 제거

이렇게 제거한 이후에는 버튼을 아무리 클릭해도 반응이없습니다.

DOM레벨 2 이벤트 핸들러

DOM 레벨 2 이벤트에서는 모든 DOM노드에 존재하는 이벤트 메서드를 정의했습니다.

  • addEventListener()
  • removeEventListener()

매개변수로는 세가지를 받으며 아래와 같습니다.

  1. 이벤트 이름
  2. 이벤트 핸들러 함수
  3. 이벤트 핸들러를 캡처단계에서 호출할지(true) 버블링 단계 호출(false) 인지 나타내는 불리언 값
var btn = document.getElementById("myBtn");
btn.addEventListener("click", function(){
    alert(this.id);
}, false);

//하나더 추가가 가능합니다.
btn.addEventListener("click", function(){
    alert("Hello!");
}, false);

위와같이 사용 가능하며, 특장점으로 이벤트 핸들러를 추가 가능하므로 같은 이벤트라도 이벤트 핸들러가 여러개 존재할 수 있습니다.
위 예제는 이벤트를 실행될 떄 id를 보여주고 Hello! 문구를 보여주게 됩니다.

이벤트 핸들러 제거 시, addEventListener()로 추가한 이벤트 핸들러는 removeEventListener()로 제거해줘야 합니다.
하지만 addEventLister()와 removeEventListener()의 매개변수가 완전히 일치해야 삭제가 되기 때문에 위 예제에서 처럼 익명함수로 정의했을 경우 삭제되지 않습니다.
아래 예제처럼 사용하면 삭제 가능합니다.

var handler = function(){
    alert(this.id);
};

var btn = document.getElementById("myBtn");
btn.addEventListener("click", handler, false);

//하나더 추가가 가능합니다.
btn.removeEventListener("click", handler, false); //삭제됨

이벤트 핸들러가 버블링 단계에서 동작해야 하기때문에 이 방법을 가장 많이 사용합니다.
캡처단계에 추가하는건 필요 이벤트가 타깃에 도달하기 전에 가로채야 할 때 적합하지만 그럴 경우가 아니라면 이벤트 버블링을 사용하길 권장합니다.

IE 이벤트 핸들러

IE는 DOM표준과 비슷한 attachEvent(), detachEvent()두 메서드를 구현했습니다.

var btn = document.getElementById("myBtn");
btn.attachEvent("onclick", function(){
    alert("clicked");
})

addEventListener와의 차이점은 this가 window입니다.

detachEvent()도 removeEventListener()와 같이 생성때와 동일한 매개변수를 사용하여 제거 가능합니다.

크로스 브라우저 이벤트 핸들러

브라우저별 차이를 메우기 위해 자바스크립트 라이브러리나 커스텀코드를 직접 작성합니다.
커스텀 코드를 만드는 것에 대해서 알아봅시다.
addHandler()메서드와 removeHandler()메서드를 추가 하고 이를 관리하는 EventUtil객체를 만들어 봅시다.

var EventUtil = {
    addHandler: function(element, type, handler){
        if(element.addEventListener){
            element.addEventListener(type, handler, false);
        }else if (element.attachEvent){
            element.attachEvent("on" + type, handler);
        }else{
            element["on" + type] = handler;
        }
    },
    removeHandler: function(element, type, handler){
        if(element.removeEventListener){
            element.removeEventListener(type, handler, false);
        }else if (element.detachEvent){
            element.detachEvent("on" + type, handler);
        }else{
            element["on" + type] = handler;
        }
    }
}

//넘겨받은 요소에 메서드가 존재하는지 체크한후 사용하는 방법으로 크로스 브라우징을 지원합니다.
//접두사 on이 필요한 경우 추가해 줍니다
//위 객체는 아래와 같이 사용할 수 있습니다.

var btn = document.getElementById("myBtn");
var handler = function(){
    alert("Clikced");
};

//추가
EventUtil.addHandler(btn, "click", handler);

//제거
EventUtil.removeHandler(btn, "click", handler);

IE이벤트 핸들러 브라우저에 스코프 문제는 해결되지 않지만, 이벤트 핸들러를 추가하고 제거하는 기능은 문제없이 제공합니다.

마치며

간단하게 몇가지 이벤트 사용 법을 알아보고, 이벤트의 흐름과 핸들러 그리고 크로스 브라우징 지원까지 알아보았습니다.
다음 포스트에서는 이벤트 객체에 대해서 자세히 알아보겠습니다.


이 포스트는 프론트엔드 개발자를 위한 자바스크립트(인사이트)에서 발췌한 내용이 포함되어 있습니다.
내용 전문이 아니기 때문에 자세하게 알고싶으신 분은 프론트엔드 개발자를 위한 자바스크립트(인사이트) 서적을 참고 하시길 바랍니다.