MDN’s new design is in Beta! A sneak peek: https://blog.mozilla.org/opendesign/mdns-new-design-beta/

EventTarget.addEventListener()

EventTarget.addEventListener() 메소드는 EventTarget 에서 불려질 특정 리스너를 등록합니다. 이벤트의 대상은 다큐먼트 안의 ElementDocument 자신, Window, 또는 XMLHttpRequest 같은 이벤트를 지원하는 어떤 객체일 것입니다.

구문

target.addEventListener(type, listener[, useCapture]);
target.addEventListener(type, listener[, useCapture, wantsUntrusted  ]); // Gecko/Mozilla only
type
등록할 event type 을 나타내는 문자열
listener
특정 타입의 이벤트가 발생할 때 알림을 받을 객체. 반드시 EventListener 인터페이스를 수행하는 객체이거나, Javascript function 이어야 합니다.
useCapture Optional
만약 true라면, useCapture 는 사용자가 capture를 초기화하길 원한다는 것을 나타냅니다. capture를 초기화 한 후, 모든 지정된 타입의 이벤트는 DOM tree에서 하위의 어떤 EventTarget에 전달되기 전에 등록된 리스너에게 먼저 전달될 것입니다.  tree의 상위로 버블링된 이벤트는 capture를 사용하기 위해 지정된 리스너를 동작시키지 않을 것입니다. 이벤트 버블링과 캡처링은 다른 엘리먼트를 포함하는 중첩된 한 엘리먼트에서, 두 엘리먼트에 이벤트를 위한 핸들이 등록되어 있을 때 이벤트를 전파하는 두 가지 방법입니다. 이벤트 전파 모드는 엘리먼트들이 이벤트를 받는 순서를 결정합니다. DOM Level 3 Events 와 JavaScript Event order 에 자세한 설명이 있습니다. 만약 지정하지 않는다면, userCaptrue의 default는 false입니다.
Note: 이벤트 타겟에 등록된 이벤트 리스너는 캡처링과 버블링 페이즈라기보다는 타겟 페이즈안의 이벤트입니다. 타겟 페이즈안의 이벤트들은 useCapture 값에 상관없이 엘리먼트의 모든 리스너를 동작시킵니다.
Note: useCapture 는 주요 브라우저들의 최신 버전에서만 선택할 수 있게 되었습니다. 예로, Firefox 6 이전에는 선택적인것이 아니었습니다. 폭넓은 호환성을 위해 여러분은 이 파라미터를 제공해주어야 합니다.
wantsUntrusted
만약 true라면, 리스너는 웹 컨텐트에 의해 전달된 종합적인 이벤트를 받을것입니다(Chrome에서는 default가 false, 일반적인 웹 페이지들에서는 true). 이 파라미터는 Gecko에서만 사용가능하고, add-ons와 브라우저 자체의 코드에게 주로 유용하게 쓰입니다. Interaction between privileged and non-privileged pages 에서 예시들을 확인해보세요.

예시

간단한 리스너 추가하기

HTML Content

<table id="outside">
    <tr><td id="t1">one</td></tr>
    <tr><td id="t2">two</td></tr>
</table>

JavaScript Content

// Function to change the content of t2
function modifyText() {
  var t2 = document.getElementById("t2");
  if (t2.firstChild.nodeValue == "three") {
    t2.firstChild.nodeValue = "two";
  } else {
    t2.firstChild.nodeValue = "three";
  }
}

// add event listener to table
var el = document.getElementById("outside");
el.addEventListener("click", modifyText, false);

위 예시에서, modifyText() 는 addEventListener() 를 사용해 등록된 click 이벤트를 위한 리스너 입니다. 테이블 안의 클릭은 핸들러에게 전달되고 modifyText() 를 실행합니다.

만약 리스너 함수에게 파라미터를 전달하고 싶다면, 익명의 함수를 사용하면 됩니다.

익명의 함수와 이벤트 리스너

HTML Content

<table id="outside">
    <tr><td id="t1">one</td></tr>
    <tr><td id="t2">two</td></tr>
</table>

JavaScript Content

// Function to change the content of t2
function modifyText(new_text) {
  var t2 = document.getElementById("t2");
  t2.firstChild.nodeValue = new_text;    
}
 
// Function to add event listener to table
var el = document.getElementById("outside");
el.addEventListener("click", function(){modifyText("four")}, false);

Notes

왜 addEventListener를 사용하는가?

addEventListener 는 W3C DOM에 명시된 이벤트 리스너를 등록하기 위한 방법입니다. 장점은 다음과 같습니다:

  • 하나의 이벤트에대해 하나 이상의 핸들러를 추가하도록 해줍니다. 부분적으로 DHTML 라이브러리나 Mozilla extensions 같은 다른 라이브러리/익스텐션과 잘 동작해야 하는 것들을 위해서도 유용하게 쓰입니다.
  • 리스너가 활성화될 때 페이즈의 더 나은 컨트롤을 제공합니다 (캡처링 vs. 버블링)
  • HTML 엘리먼트뿐만 아니라, 어떤 DOM 엘리먼트에서든 동작합니다.

아래에 설명한 older way to register event listeners 를 보셔도 됩니다.

이벤트 전달 중에 리스너 추가하기

만약 EventListener 가 이벤트의 처리 도중에 EventTarget 에 추가되었다면, 이벤트는 리스너를 실행시키지 않습니다. 하지만, 버블링 페이즈처럼 이벤트 흐름의 후반 단계동안에는 리스너가 동작할 것입니다.

동일한 여러개의 이벤트 리스너

만약 동일한 여러개의 EventListeners 가 동일한 파라미터로 동일한 EventTarget 에 등록되었다면, 중복된 항목들은 버려집니다. EventListener 는 두번 불려지지 않고, removeEventListener 메소드를 사용해 직접 제거할 필요도 없습니다.

핸들러 안의 this

유사한 엘리먼트들의 집합을 위한 포괄적인 핸들러를 사용할때 처럼, 이것은 이벤트 핸들러가 동작한 엘리먼트를 참조하기 위해 자주 가치있게 쓰입니다.

addEventListener() 를 사용해 엘리먼트에 핸들러 함수를 추가할 때, 핸들러 안의 this 의 값은 엘리먼트의 레퍼런스입니다. 이것은 핸들러에게 전달되는 이벤트 아규먼트의 currentTarget 프로퍼티의 값과 같습니다.

만약 한 이벤트 어트리뷰트(예로 onclick)가 HTML 코드의 한 엘리먼트에 명시되면, 어트리뷰트 값 안의 JavaScript 코드는 addEventListener() 의 사용과 함께 일관된 방식으로 this 의 값을 연결하고 있는 핸들러 함수 안에 효과적으로 감싸집니다. 코드 안의 this 의 존재는 엘리먼트의 레퍼런스를 나타냅니다. 어트리뷰트 값 안의 코드에 의해 호출된 함수안의 this 의 값은 standard rules 에 따라 동작한다는 것을 기억하세요. 그러므로, 주어진 다음 예시:

<table id="t" onclick="modifyText();">
  . . .

onclick 이벤트를 통해 호출될 때, modifyText() 안의 this 의 값은 전역 (window) 객체(또는 strict mode의 경우에는 undefined)의 레퍼런스입니다.

Note: JavaScript 1.8.5 는 주어진 함수에 대한 모든 호출에서 this 로써 사용되어야 하는 값을 여러분이 지정하도록 하는 Function.prototype.bind() 메소드를 소개하고 있습니다. 이 메소드는 여러분의 함수가 호출될때의 컨텍스트에 따라 무엇이 될지 불확실한 곳에서의 문제를 쉽게 우회할수 있도록 해줍니다. 하지만, 이후에 제거할 수 있도록 리스너 주위에 리스너를 유지할 필요가 있다는 것을 기억하세요.

이것은 bind 를 쓰고, 안쓴 예시입니다:

var Something = function(element) {
  this.name = 'Something Good';
  this.onclick1 = function(event) {
    console.log(this.name); // undefined, as this is the element
  };
  this.onclick2 = function(event) {
    console.log(this.name); // 'Something Good', as this is the binded Something object
  };
  element.addEventListener('click', this.onclick1, false);
  element.addEventListener('click', this.onclick2.bind(this), false); // Trick
}

위 예시안의 문제는 bind 된 리스너를 여러분이 제거할 수 없다는 것입니다. 다른 해결 방법은 어떤 이벤트든 캐치할 수 있는 handleEvent 라 불리는 특별한 함수를 사용하는 것입니다:

var Something = function(element) {
  this.name = 'Something Good';
  this.handleEvent = function(event) {
    console.log(this.name); // 'Something Good', as this is the Something object
    switch(event.type) {
      case 'click':
        // some code here...
        break;
      case 'dblclick':
        // some code here...
        break;
    }
  };

  // Note that the listeners in this case are this, not this.handleEvent
  element.addEventListener('click', this, false);
  element.addEventListener('dblclick', this, false);

  // You can properly remove the listeners
  element.removeEventListener('click', this, false);
  element.removeEventListener('dblclick', this, false);
}

Internet Explorer 의 유산과 attachEvent 

IE 9 이전의 인터넷 익스플로러 버전들에서, 여러분은 표준인 addEventListener 보다는 attachEvent 를 사용해야만 했습니다. IE를 위해, 이전의 예시를 이렇게 수정하세요:

if (el.addEventListener) {
  el.addEventListener('click', modifyText, false); 
} else if (el.attachEvent)  {
  el.attachEvent('onclick', modifyText);
}

attachEvent 에는 문제점이 있는데, this 의 값이 호출된 엘리먼트의 값 대신 window 객체의 레퍼런스라는 것입니다.

호환성

스크립트의 시작에 다음과 같은 코드를 사용하면 IE 8에서 지원하고 있지 않은 addEventListener, removeEventListener, Event.preventDefault and Event.stopPropagation 를 비슷하게 사용할 수 있습니다. 이 코드는 handleEvent 와 DOMContentLoaded 이벤트 사용을 지원합니다.

Note: useCapture 는 IE 8 에서 어떠한 대체할만한 것이 없으므로 지원되지 않습니다. 아래 코드는 IE 8 의 지원을 위한다는 것을 기억하세요.

또한 이 IE 8 polyfill 은 오직 표준 모드에서만 동작합니다: doctype 명시가 필수적입니다.

(function() {
  if (!Event.prototype.preventDefault) {
    Event.prototype.preventDefault=function() {
      this.returnValue=false;
    };
  }
  if (!Event.prototype.stopPropagation) {
    Event.prototype.stopPropagation=function() {
      this.cancelBubble=true;
    };
  }
  if (!Element.prototype.addEventListener) {
    var eventListeners=[];
    
    var addEventListener=function(type,listener /*, useCapture (will be ignored) */) {
      var self=this;
      var wrapper=function(e) {
        e.target=e.srcElement;
        e.currentTarget=self;
        if (typeof listener.handleEvent != 'undefined') {
          listener.handleEvent(e);
        } else {
          listener.call(self,e);
        }
      };
      if (type=="DOMContentLoaded") {
        var wrapper2=function(e) {
          if (document.readyState=="complete") {
            wrapper(e);
          }
        };
        document.attachEvent("onreadystatechange",wrapper2);
        eventListeners.push({object:this,type:type,listener:listener,wrapper:wrapper2});
        
        if (document.readyState=="complete") {
          var e=new Event();
          e.srcElement=window;
          wrapper2(e);
        }
      } else {
        this.attachEvent("on"+type,wrapper);
        eventListeners.push({object:this,type:type,listener:listener,wrapper:wrapper});
      }
    };
    var removeEventListener=function(type,listener /*, useCapture (will be ignored) */) {
      var counter=0;
      while (counter<eventListeners.length) {
        var eventListener=eventListeners[counter];
        if (eventListener.object==this && eventListener.type==type && eventListener.listener==listener) {
          if (type=="DOMContentLoaded") {
            this.detachEvent("onreadystatechange",eventListener.wrapper);
          } else {
            this.detachEvent("on"+type,eventListener.wrapper);
          }
          eventListeners.splice(counter, 1);
          break;
        }
        ++counter;
      }
    };
    Element.prototype.addEventListener=addEventListener;
    Element.prototype.removeEventListener=removeEventListener;
    if (HTMLDocument) {
      HTMLDocument.prototype.addEventListener=addEventListener;
      HTMLDocument.prototype.removeEventListener=removeEventListener;
    }
    if (Window) {
      Window.prototype.addEventListener=addEventListener;
      Window.prototype.removeEventListener=removeEventListener;
    }
  }
})();

이벤트 리스너를 등록하는 오래된 방법

addEventListener() 는 DOM 2 Events 명세와 함께 소개되었습니다. 그 전에는, 이벤트 리스너는 다음과 같이 등록되었습니다:

// 함수 레퍼런스 전달 — 함수를 부를 때 끝에 '()'를 추가하지 마세요!
el.onclick = modifyText;

// 함수 표현 사용
element.onclick = function() {
  // ... 함수 로직 ...
};

이 메소드는 엘리먼트에 존재하는 click 이벤트 리스너가 있다면 대체합니다. 유사하게 다른 이벤트와 blur (onblur), keypress (onkeypress) 등과 같은 연관된 이벤트 핸들러에게도 적용됩니다.

왜냐하면 이것은 근본적으로 DOM 0 의 일부였고, 이 메소드는 아주 광범위하게 지원되었으며 아무런 크로스 브라우저 코드를 요구하지도 않습니다. 따라서 이것은 addEventListener() 의 추가적인 기능이 요구되지 않는다면 이벤트 리스너를 동적으로 등록하는데 일반적으로 사용됩니다.

메모리 문제들

var i;
var els = document.getElementsByTagName('*');

// Case 1
for(i=0 ; i<els.length ; i++){
  els[i].addEventListener("click", function(e){/*do something*/}, false);
}

// Case 2
function processEvent(e){
  /*do something*/
}

for(i=0 ; i<els.length ; i++){
  els[i].addEventListener("click", processEvent, false);
}

첫 번째 케이스에서, 새 (익명의) 함수는 각 반복에서 생성되었습니다. 두 번째 케이스에서, 이전에 명시된 같은 함수는 이벤트 핸들로로써 사용되었습니다. 이것은 더 작은 메모리 사용의 결과를 냅니다. 게다가, 첫 번째 케이스에서, 익명의 함수에 아무런 레퍼런스가 유지되지 않는다면, 핸들러에 레퍼런스가 없기 때문에 element.removeEventListener 호출은 불가능합니다. 반면에, 두 번째 케이스에서 myElement.removeEventListener("click", processEvent, false) 를 하는 것은 가능합니다.

명세

명세 상태 코멘트
DOM
The definition of 'EventTarget.addEventListener()' in that specification.
Living Standard  
Document Object Model (DOM) Level 3 Events Specification
The definition of 'EventTarget.addEventListener()' in that specification.
Working Draft  
Document Object Model (DOM) Level 2 Events Specification
The definition of 'EventTarget.addEventListener()' in that specification.
Recommendation 최초 정의

브라우저 호환성

Feature Chrome Firefox (Gecko) Internet Explorer Opera Safari (WebKit)
Basic support 1.0[1] 1.0 (1.7 or earlier)[2] 9.0 7 1.0[1]
useCapture made optional 1.0 6.0 9.0 11.60 (Yes)
Feature Android Firefox Mobile (Gecko) IE Mobile Opera Mobile Safari Mobile
Basic support 1.0 1.0 (1.0)[2] 9.0 6.0 1.0[1]

[1] WebKit 이 2011년 6월 에 useCapture 파라미터를 [optional] 에 명시적으로 추가했지만, 이것은 추가되기 전부터 동작해왔습니다. 새 변경 사항은 Safari 5.1 과 Chrome 13 에도 전파되었습니다.

[2] Chrome 49 이전에는, 타입과 리스터 파라미터들은 옵션이었습니다.

[3] Firefox 6 이전에는, 만약 userCapture 파라미터가 명시적으로 false 가 아니면 브라우저는 error 를 throw 했습니다. Gecko 9.0 (Firefox 9.0 / Thunderbird 9.0 / SeaMonkey 2.6) 이전에는, 만약 listner 의 파라미터가 null 이면 addEventListener() 는 예외를 throw 했습니다. 지금 이 메소드는 에러를 반환하지는 않지만, 어떤 동작을 하지도 않습니다.

[4] Internet Explorer 의 옛 버전들은 자신들이 가진 EventTarget.attachEvent 메소드를 대신 지원합니다.

[5] 하위 호환성을 위해, options 를 허용하는 브라우저들은 세 번째 파라미터로 options 또는 Boolean를 지원합니다..

참고

문서 태그 및 공헌자

 이 페이지의 공헌자: cs09g
 최종 변경: cs09g,