2017년 1월 30일 월요일

[JavaScript] 자바스크립트 이벤트 핸들링(Event Handling)

Events란 사용자, 또는 브라우저에 의해 실행되는 행위들을 말한다. 이러한 event들에는 click, load, mouseover, focus 등이 있다. Event가 있으면 이들을 사용하는 것도 있는데, event에 반응하는 함수(function)들을 event handler, 또는 event listener라 부른다. event listener 들의 이름은 간단하다. Onclick, onload, onmouseover, onfocus처럼 원하는 event에 on을 붙이면 된다.
이러한 Event Handler들을 등록하는데 3가지 방법이 있다

HTML Event Handler



<div clicked="" onclick="(function(){alert(onmouseover=alert('mouseover')">
</div>

원하는 event 프로퍼티에 javascript statement들을 적어주면 된다. Javascript statement가 들어가기 때문에, self executing function 또는 함수 호출 또한 적을 수 있다. 주의할 점은, 자바스크립트 코드들이 HTML의 프로퍼티 값으로 들어가게되어, HTML 문법에 포함되는 문자들을 사용해서는 안된다. 예를들어, <, >, “, 등을 escape character 가 아닌 방식으로 사용하게 되면 페이지에 에러가 발생할 수도 있다. 그러니 " < > 등의 특수문자 코드를 이용하여야 한다.

이런 방식으로 등록된 event handler들은 attribute란에 있는 자바스크립트 구문들로 function을 생성하는것과 같은 효과를 내게 된다. 관련 event 객체에 접근할 수 있는 event라는 로컬 변수를 가지게 되며, event 객체를 새로 선언하거나 argument 리스트에서 객체를 찾아오지 않고도 event 객체를 사용할 수 있게 해준다.


<div onclick="”(function(){alert(event.type);})()”">
</div>

또한 이렇게 생성된 함수 scope내의 this 값은 event handler가 등록된 html element가 되며, document 객체와 this 객체 내부 변수들을 로컬 변수처럼 사용할 수 있다. 이는 html event handler 가 지금은 deprecated 된 with 문을 이용해 함수를 구성하기 때문이다.


<div onclick="”(function(){alert(this.type);})()”">
</div>
//위의 코드는 아래와 같다
 
function(){
with(document){
with(this){
alert(this.type):
}
}
}

그러므로 this.type을 그냥 type으로 교체해도 함수는 잘 작동하게된다. 또 이로인해서 event handler 내에서 이벤트가 발생한 html 엘리멘트의 프로퍼티에 더 쉽게 접근할 수 있다.

HTML event handler의 단점을 말하자면, 첫번째로 시점 문제를 들 수 있다. 예전에 script defer, async 태그 글에서 말했던것 처럼, script가 로딩과 html 페이지 로딩, 렌더링은 모두 같은 시점에서 완료되지 않는다. 만약 우리가 html event handler에서 별도의 스크립트 파일 내의 함수를 호출하게 될때. Script 가 더 늦게 로딩된다면 웹브라우져는 그 함수를 찾지 못하고 에러를 발생시키게 될것이다. 그렇기 때문에 대부분의 html event handler들은 try catch문을 필수적으로 사용하게 된다.

또다른 단점으로는 script와 html간의 의존성이다. Html안에 script가 들어간 형식이기 때문에 script의 수정은 바로 html의 수정으로 이어지고, 역으로 html의 수정은 script의 수정으로 이어지게 된다. 이때문에 코드가 조금만 복잡해져도 일이 2배가 되니, 가능하면 html event handler의 사용을 피하도록 하자.


DOM Level 0 Event handler


HTML Element 객체를 호출해 객체의 프로퍼티에 어싸인하는 방식이다.

var button = document.getElementById(“login”);
button.onclick = function(){
alert(this.id); // “login”
process_login();
}

window, document 객체를 포함한 모든 Element 객체는 이벤트 핸들러 프로퍼티를 가지고 있다. 단 모든 이벤트 핸들러 프로퍼티는 모두 소문자로 되어있으며(onclick, onfocus, etc.), 이 프로퍼티에 함수를 어싸인하여 이벤트를 핸들할 수 있다.

위의 예제에서는 login이라는 id를 가진 html element 객체가 호출되었고, 해당 객체의 onclick 프로퍼티에 함수를 어싸인하였다. Event handler 가 스크립트 상에서 어싸인되기때문에, 해당 코드가 실행될때까지는 이벤트가 발생해도 핸들링 되지 않는다. 또한, 어싸인된 이벤트핸들 함수는 이벤트가 발생한 HTML Element 객체의 프로퍼티이기 때문에 그 함수내에서 this 값은 이벤트가 발생한 Element 객체와 같다.

이렇게 어싸인된 이벤트 핸들러를 제거하려면 해당 프로퍼티에 null 값을 어싸인해주면 된다

button.onclick = null;

이 이벤트핸들러는 DOM Level 0 에서 소개되었기 때문에 그냥 DOM Level 0 Event handler로 불린다.

DOM Level 2 Event Handler


DOM Level 0 에서 객체의 프로퍼티를 이용해 이벤트 핸들러를 어싸인한것과 달리, DOM Level 2 에서는 2개의 함수를 이용해 이벤트핸들러를 추가, 제거하는 방법을 소개했다. 모든 DOM node들에는 addEventListener() 함수와 removeEventListener()함수가 존재하며 이 함수들은 핸들할 이벤트 이름, 핸들 함수, 그리고 Event Capture 단계에서 이벤트를 핸들할지(true), Bubble단계에서 이벤트를 핸들할지(false) 여부에 대한 Boolean 값을 포함해 총 3개의 argument들을 받는다.

예를들어 bubble 단계에서 click 이벤트를 핸들할 함수를 어싸인하는것은 아래아 같다


var button = document.getElementID(“button”);
var showAlert =  function(){
alert(“you clicked” + this.id);
}
button.addEventListener(“click”,showAlert,false);

DOM Level 0 과 같이 this 값은 해당 element 객체와 같다.

DOM Level 2 방식의 가장 큰 장점은 1개의 이벤트에 여러개의 이벤트 핸들러를 어싸인 가능하다는 것이다.


var button = document.getElementID(“button”);
var showAlert =  function(){
alert(“you clicked” + this.id);
}
button.addEventListener(“click”,showAlert,false);
button.addEventListener(“click”,function(){alert(“second message”);},false);

위의 경우에 이벤트 핸들러는 추가된 순서대로 실행되게 된다.

DOM Level 2 방식으로 추가된 핸들러들은 DOM Level 2 방식으로만 제거될 수 있다. 즉 addEventListner 함수로 추가한 이벤트 핸들러가 있다면 같은 argument로 호출된 removeEventHandler만이 제거할 수 있다는 뜻이다. 그렇기 때문에 만약 익명함수의 이벤트 핸들러를 추가했다면, 그 이벤트 핸들러의 제거는 불가능 하다는 뜻이기도 하다.


var button = document.getElementID(“button”);
var showAlert =  function(){
alert(“you clicked” + this.id);
}
button.addEventListener(“click”,showAlert,false);
button.addEventListener(“click”,function(){alert(“second message”);},false);
button.removeEventListener(“click”,showAlert,false);
button.removeEventListener(“click”,function(){alert(“second message”);},false);

위의 예제에서 첫번재 이벤트 핸들러는 제거되었지만 2번째 이벤트 핸들러는 제거되지 않았다. 2번째 이벤트 핸들러 제거를 위해 완벽히 일치하는 argument를 넘긴것 같지만, 지난 함수 글에서 말했듯 함수는 객체이고, 새로운 객체인 anonymous 함수를 생성하였기 때문에 2번째 핸들러는 제거되지 않는다.