티스토리 뷰

* 자바스크립트에서는 이벤트를 다루는 방법은 대표적으로 3가지를 꼽을 수 있다.


    • 1) HTML에 inline으로 등록
<div onclick="alert('clicked!');">Click me!</div>


    • 2) element의 onload 속성을 통한 등록

<div id="domId">Click me!</div>
<script>
var element = document.getElementById("domId");
element.onclick = function () {
    alert("clicked!");
}
</script>

    • 3) addEventListener/attachEvent를 이용한 등록

<div id="domId">Click me!</div>
<script>
var element = document.getElementById("domId");
element.addEventListener("click", function () {
    alert("clicked!");
}, false);
</script>


: 일단 1)번부터 보면 이건 초창기 인터넷 넷스케이프 2 당시부터 사용하던 이벤트 핸들러 방식이다. 워낙에 전통적인 방법으로 오랫동안 사용해와서 많은 사람들이 아직도 사용하고 있다. 하지만 HTML을 사파리 reader 등으로 볼 때 문서로서 최적화하기 위하여, MVC 또는 MVVM 모델을 분류하기 위하여 html은 오로지 document를 표시해주는 역할을 취하게 하기 위해서는 1)번 보다는 이벤트 핸들러는 자바스크립트로 설정하는 2)번과 3)번 방법이 최근에 웹 개발자들 사이에서 자주 사용하는 방법이다. 만약 jquery를 이용한다고 저렇게도 안한다고 말한다면 당신은 3)번 방법을 내부적으로 이용하고 있는 것이다.


: 하지만 위의 2)번과 3)번 두 가지는 기능상으로는 차이를 못느끼겠지만, 3)번 방법은 익스플로러가 IE10 이전 버전에서는 addEventListener를 지원하지 않고 독자적인 attachEvent를 활용하기 때문에 크로스 브라우져를 고민해야하는 문제점이 있고, 2)번 방법은 DOMElement.onload 등으로 하면 바로 크로스브라우져를 지원하는 이벤트 리스너를 등록할 수 있다. 하지만 최근에 들어서는 일반적으로는 2)번 방법보다는 좀 불편한 3)번 방법을 더 선호하고 있는데 어떠한 부분에서 선호하는 것일까 생각해보자.

 


 

* onload의 사용은 global 변수의 사용과 같다

: 일단 2)번 방법의 사용을 자제해야하는 이유라면 global 변수의 사용을 자제해야 하는 이유와 비슷하다. 사실 onload나 글로벌 변수를 사용하면 매우 편하고 그렇지만, 다른 라이브러리들과의 충돌을 피하기 위해 배려를 해주고 본인이 모듈화를 하거나 라이브러리화를 할 때에도 사용 안하는 것이 기본이다. 이것이 무엇을 뜻하는지 아래의 예를 살펴보면 그 차이를 느낄 수 있을 것이다.


<div id="div1">Click me!</div>
<script>
document.getElementById("div1").onclick = function (e) { alert("do process #1"); };
document.getElementById("div1").onclick = function (e) { alert("do process #2"); };
</script>


Click me!

: 위와 같은 경우 클릭 이벤트에 2가지의 프로세스를 추가하려고 했지만, 클릭을 해보면 #2만 출력되는 것을 확인할 수 있다. 반면, addEventListener를 테스트해보면 아래와 같다.


<div id="div2">Click me!</div>
<script>
document.getElementById("div2").addEventListener("click", function (e) { alert("do process #1"); });
document.getElementById("div2").addEventListener("click", function (e) { alert("do process #2"); });
</script>

Click me!

: 2개의 알림창이 순서대로 뜨는 것을 확인할 수 있다. 이렇게 2개의 프로세스를 같은 이벤트에 처리해주고 싶을 때에는 addEventListener/attachEvent 함수를 사용하면 된다. 하지만 과연 하나의 이벤트에 2개 이상의 프로세스를 처리 해야하는 경우가 과연 많이 있을까 생각해보면, 일반적으로는 많지 않지만 다른 이벤트보다도 load 이벤트에 대한 처리는 항상 조심해야한다. 만약 라이브러리에서 onload 함수를 직접 속성으로 설정하면서 변수들을 초기화한다면, 이것은 내가 짜놓은 onload 함수를 덮어 씌워서 위처럼 내가 원하는 프로세스를 밟지 못하게 된다.


: 또한 다른 점은 외부에서 dom element의 속성을 직접 수정할 수 있기 때문에 웹페이지에서 개발자 콘솔로 쉽게 접근하여 수정할 수 있게 된다. 따라서 이것은 보안상에서도 다소 안 좋은 경향을 드러내는 것이 글로벌 변수를 자제해야하는 이유와 비슷하게 생각할 수 있게 된다. 다른 라이브러리와의 충돌을 피하고, 보안상으로도 자제를 하는 것이 좋을 때가 있는 것이다.


: 물론 외부 라이브러리를 활용 안하고 독자적인 개발이라면 그리고 보안상으로도 중요하지 않은 이벤트 처리 프로세스라면 굳이 신경쓸 필요는 없을 것이다. 하지만 개발자들이 점점 글로벌 변수의 사용을 일반적으로 싫어하듯 자바스크립트 개발자들도 직접 속성을 통해 이벤트를 등록하는 것을 일반적으로 싫어하게 되는 방향으로 가지 않을까 생각해본다. 



* 이벤트 처리 순서의 조정: capture vs bubble

: 3)번 방법이 우위에 있는 또 다른 이유가 있다면 이벤트를 capture 방식으로 처리하기 위해 addEventListener의 3번째 인자를 설정해줘서 이벤트가 동작하는 방식을 바꿔줄 수 있다는 점이다. 이것을 조정함으로써 상위의 엘레멘트에서 이벤트를 캐치하느냐, 하위의 엘레멘트부터 타고 올라오느냐의 차이를 만들어낼 수 있는데, 이 차이를 느끼려면 event delegation 패턴을 사용해보면 가끔은 상위의 엘레멘트에서 이벤트를 막아야하는 경우가 발생할 수도 있다는 것을 느낄 수 있을 것이다. 특히, focus와 blur와 contenteditable의 조합으로 이것저것 하다보면 이벤트가 하위엘레멘트부터 타고 들어오는 것이 아니라 상위에서부터 걸러내야할 필요가 있을 때가 있다. 만약 contenteditable을 설정한 div에 blur나 focus 이벤트를 받아서 처리하고 싶다면 반드시 capture 방식으로 설정해야한다.


<div id="delegate">
    <div id="content" contenteditable>Change and prevent to blur</div>
</div>
<script>
document.getElementById("delegate").addEventListener("blur", function (e) {
    alert("blur");
}, true);
</script>


: 위와 같은 경우는 capture 방식을 사용하고 있지만, 만약 bubble 방식이나 onblur로 설정을 하게 되면 이벤트를 처리하지 못하고 div에서 이벤트를 먹어버리게 된다. 물론 content에 직접 이벤트를 설정하는 경우에는 잘 동작하게 되겠지만, event delegation 패턴을 자주 이용할만한 복잡한 사이트라면 위의 상황은 충분한 이슈가 된다.



* 모질라 개발자들의 추천은?

: 그 외 addEventListener를 왜 써야하는가는 모질라 사이트에도 설명이 나와있는데,


https://developer.mozilla.org/en/docs/DOM/element.addEventListener#Why_use_addEventListener.3F 


: 여기서 나온 첫번째 이유는 비슷하다. 라이브러리와 확장 플러그인들을 활용하는데 좋다는 점, 두번째도 이벤트 리스너가 발생하는 시기를 조율할 수 있다는 점이 비슷하고, 그리고 세번째는 HTML 엘레멘트 뿐만 아니라 DOM 엘레멘트들에도 설정을 할 수 있다는 점을 들고 있다. 이것은 HTML 뿐만아니라 SGML, XML에도 DOM이 포함되어있으므로 이벤트의 활용의 범위가 더 넓어질 수 있는 것이다. 그리고 DOM 스펙을 찾아보니 비슷한 내용이 또 나오는 것을 확인할 수 있다. 


http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-registration 


: 여기 조금 아래에 보면 HTML4.0 이전의 이벤트 리스너와 연동을 하기 위하여 html attribute로 설정되는 이벤트 리스너와 연동을 하지만, 정식 스펙은 addEventListener로 나와있다. 그리고 조금 아래에 흥미있는 점이 아래와 같이 써 놓은 내용인데, 


'No technique is provided to allow HTML 4.0 event listeners access to the context information defined for each event.' 


: 자바스크립트의 컨텍스트를 자유자재로 활용하고 closure를 활용하다보면 이벤트 발생시 사용할 정보들을 마음껏 활용하고 싶을때가 있는데, 이럴 때 addEventListener로 하면 쉽게 컨텍스트와 closure를 조율할 수 있다. 사실 .onload와 같이 할 때에도 컨텍스트와 closure는 똑같이 설정할 수 있지만 여기서는 html에 inline으로 이벤트를 등록하는 것을 이야기하고 있다. 즉, 1)번 방법으로 등록을 하게 되면 이렇게 제한적으로 또는 불편하게 이벤트를 설정해야한다.



* 크로스브라우져 이벤트 등록

: 결과적으로 브라우져별로 고민하고 싶지 않다면 속시원하게 onload등을 이용할 수도 있지만, 사실 event를 크로스 브라우져로 등록할 수 있는 5~7줄짜리 함수도 이미 널리 퍼져있기도 하고 라이브러리들을 이용하면 알아서 해주기도 하니까 많은 고민을 하지는 않게 되었다. 아래가 아주 쉽게 활용가능한 이벤트 핸들러 등록 함수이다.


function addEvent(element, eventName, cb, isCapture) {
    if (window.addEventListener) {
        element.addEventListener(eventName, cb, isCapture);
    } else if (window.attachEvent) {
        element.attachEvent("on" + eventName, cb);
    } else {
        element["on" + eventName] = cb;
    }
}

var element = document.getElementById("div3");
addEvent(element, "click", function (e) {alert("clicked!");}, false);


: IE에서 사용하는 attachEvent에는 on까지 붙여줘야한다는 것을 주의하고, 함수가 존재하면 addEventListener부터 순서대로 적용하고, 마지막에는 최후의 보루로 직접 속성을 설정하는 방법을 택하는 함수다. 이것 함수는 매번 addEvent로 추가할 때에 if-else를 해야하는 불편함이 있으므로 즉시 호출 함수를 이용한다면 더 성능이 좋은 함수를 쉽게 구현할 수 있을 것이다.


var addEvent = (function (window) {
    if (window.addEventListener) {
        return function (element, eventName, cb, isCapture) {
            element.addEventListener(eventName, cb, isCapture);
        };
    } else if (window.attachEvent) {
        return function (element, eventName, cb) {
            element.attachEvent("on" + eventName, cb);
        };
    } else {
        return function (element, eventName, cb) {
            element["on" + eventName] = cb;
        };
    }
}(window));


: 다음과 같이 한다면 처음에 한번 실행될 때 어떠한 이벤트 리스너 등록 함수가 있는지 찾은 다음에 addEvent에 대하여 그 함수를 설정을 하게 되는 것이다. 이렇게 한다면 조금 더 퍼포먼스가 좋은 크로스 브라우져 이벤트 등록 함수를 구성할 수 있을 것이다.

 

- 덧: 즉시 호출 함수에 대해서, 그리고 위의 함수에 대해서 이해하고 싶다면 현재 계속 올리고 있는 [속싶은 자바스크립트 강좌]를 읽으면 이해가 될 것이다!

2012/12/10 - [속깊은 자바스크립트 강좌] 시작 (예고편)




* 정리

: 어찌되었든 사실 많이 다르지도 않고 편하기로는 onload를 그냥 쓰는 것이 편하기는 하지만 리스너 등록을 하도록 되어가는 것의 이유는 자바스크립트의 역할이 많아지면서 다양한 라이브러리들이 나오고 그것들을 활용할 때 충돌이 일어나지 않게 하기 위함이 제일 크다. 초창기 베이직이나 C에서 글로벌 활용이 대중적이었다가 점점 encapsulation이 중요해진듯 자바스크립트도 그 과정을 거치고 있다고 생각하고 있고, 이처럼 된 것은 웹 페이지들이 복잡해져서 이제는 웹어플리케이션이라는 단어까지 나올 정도가 되었으니 웹페이지가 복잡하다면(또는 단순하다면) 그에 맞는 방법을 사용해야하는게 좋지 않을까 생각한다.


    • 빠른 개발, 단순한 기능, 보안상 중요하지 않은 기능, 라이브러리를 이용 안한다면 inline 또는 onload와 같이 속성을 통해 직접 이벤트 등록
    • 복잡한 기능, 안전하고 보안이 필요한 기능, 다수의 라이브러리를 활용하는 페이지 또는 라이브러리를 개발하고자 한다면 addEventListener 또는 attachEvent 함수를 활용
    • 하지만 정식 스펙인 addEventListener를 사용하는 것을 습관화 들이면 좋을 것이다.


끝.

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2024/03   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31
글 보관함