[Javascript] 이벤트 핸들러 등록 방식

Yeojin Choi·2022년 7월 5일
0

Javascript

목록 보기
10/11

브라우저는 처리해야할 특정 사건이 발생하면 이를 감지하여 이벤트를 발생시킨다.
특정 타입의 이벤트에 반응하여 어떤 일을 수행하도록 하고 싶다면, 이벤트가 발생했을 때 호출될 함수(=이벤트 핸들러) 를 브라우저에게 알려 호출을 위임(=이벤트 핸들러 등록)한다.

이벤트 타입

이벤트 타입은 이벤트의 종류를 나태내는 문자열이다. 이벤트 타입은 200여가지가 있는데 MDN 이벤트 참조 에서 확인해볼 수 있다. 버블링 될 수 있는 지, 연속해서 발생할 수 있는지, 특정 객체에서만 발생할 수 있는 지(ex. 'resize' 이벤트는 오직 window 이벤트에서만 발생한다! )에 대한 여부를 잘 확인해두자

이벤트 핸들러 등록

이벤트를 등록하는 방법은 이벤트 핸들러 어트리뷰트 방식, 이벤트 핸들러 프로퍼티 방식, addEventListener 메서드 방식이 있다.

이벤트 핸들러 어트리뷰트 방식

<button onclick="sayHi('yeojin')"> </button>
<script>
	function sayHi(name) {
		console.log(`Hi ${name}`);
	}
</script>
  • HTML 요소의 이벤트 핸들러 어트리뷰트 값으로 함수 참조가 아닌 문자열 형식의 함수 호출문을 할당하여 이벤트 핸들러를 등록한다.
  • 이벤트 핸들러 어트리뷰트 값은 암묵적으로 생성될 이벤트 핸들러의 함수 몸체를 의미한다. 즉 문자열로 전달된 함수 호출문이 파싱되어 아래 함수를 암묵적으로 생성하고, 이벤트 핸들러 프로퍼티에 할당된다.
    	function onclick(event) {
        	sayHi('yeojin');
        }
  • 어짜피 이벤트 핸들러 프로퍼티에 할당될텐데 이벤트 핸들러 프로퍼티 방식을 사용하지 않고 어트리뷰트 방식을 사용하는 이유는 무엇일까? 그 이유는 이벤트 핸들러에 인수를 전달하기 위해서다. 어트리뷰트 값으로 함수 참조를 할당해야한다면 인수를 전달하기 어렵다.
  • HTML,자바스크립트의 관심사가 다르기 때문에 해당 방식을 선호하지 않는 의굔도 있으나 Angular/React 같은 프레임워크에서는 이벤트 핸들러 어트리뷰트 방식으로 이벤트를 처리한다. HTML 과 자바스크립트를 뷰를 구성하기 위한 구성 요소로 보기 때문에 관심사가 다르다고 생각하지 않는다.

이벤트 핸들러 프로퍼티 방식

window, document, HTMLElement 와 같은 DOM 노드 객체는 이벤트 핸들러 프로퍼티를 가지고 있다.
이벤트 핸들러 프로퍼티함수를 바인딩하여 이벤트 핸들러를 등록할 수 있다.
이벤트 등록 시 이벤트를 발생시킬 객체(이벤트 타깃), 이벤트의 종류를 나타내는 문자열(이벤트 타입), 이벤트 핸들러를 지정해주어야한다.
단, 해당 방식은 하나의 이벤트 핸들러만 바인딩 할 수 있다. 여러번 바인딩 할 경우 마지막에 바인딩된 이벤트 핸들러만 실행된다.

	const button = document.querySelector('button');
	button.onclick = function () {
    	console.log('Hi yeojin');
    }

addEventListener 메서드 방식

  • EventTarget.prototype.addEventListener 메서드를 사용해 이벤트를 등록할 수 있다.
EventTarget.addEventLisener('eventType', functionName,[,useCapture])
  • EventTarget : 이벤트가 발생할 DOM Node 객체
  • eventType : 반응할 이벤트에 해당하는 문자열. on 을 붙이지 않는다.
  • functionName : 이벤트 핸들러
  • useCapture : capture 사용 여부.
    • true : 캡쳐링 시, 타깃 발생 시, 버블링 시 이벤트 캐치
    • 생략하거나 false : 타깃 발생 시, 버블링 시 이벤트 캐치
	const button = document.querySelector('button');
	button.addEventListener('click', function () {
    	console.log('Hi yeojin');
    })
  • 이벤트 핸들러 프로퍼티 방식과 addEventListener 메서드 방식을 모두 사용한다면?
    : addEventListener 메서드 방식은 이벤트 해들러 프로퍼티에 바인딩된 이벤트 핸들러에 영향을 주지 않기 때문에 2개 모두 호출된다!
	const button = document.querySelector('button');

	button.onclick = function () {
    	console.log('Hi yeojin 1');
    }

	button.addEventListener('click', function () {
    	console.log('Hi yeojin 2');
    })

	// Hi yeojin
	// Hi yeojin
  • 이벤트 핸들러 프로퍼티 방식과 달리 하나 이상의 핸들러를 등록할 수 있다.등록된 순서대로 호출된다.
	const button = document.querySelector('button');

	button.addEventListener('click', function () {
    	console.log('Hi yeojin 1');
    })

	button.addEventListener('click', function () {
    	console.log('Hi yeojin 2');
    })

	// Hi yeojin
	// Hi yeojin
  • 단, 참조가 동일한 이벤트 핸들러를 중복 등록하면 하나의 이벤트 핸들러만 등록된다.
	const button = document.querySelector('button');

	const handleClick = () => console.log('Hi yeojin');

	button.addEventListener('click', handleClick);

	button.addEventListener('click', handleClick);

	// Hi yeojin
  • addEventListener 메서드로 등록한 이벤트 핸들러를 EventTarget.prototype.removeEventListener 메서드를 사용해 제거할 수 있다. 단, addEventListener 메서드에 전달한 인수와 removeEventListener 메서드에 전달한 인수가 일치하여야한다.
	const button = document.querySelector('button');

	const handleClick = () => console.log('Hi yeojin');

	button.addEventListener('click', handleClick);

	button.removeEventListener('click', handleClick,true); // 실패
  • removeEventListener 메서드에 인수로 전달한 이벤트 핸들러는 addEventListener 메서드에 인수로 전달한 등록 이벤트 핸들러와 동일한 함수여야 한다. 따라서 익명 함수를 이벤트 핸들러로 등록한 경우 제거할 수 없다.
	const button = document.querySelector('button');

	button.addEventListener('click', () => console.log('Hi yeojin')); // 제거 불가
  • 기명 이벤트 핸들러 내부에서 removeEventListener 메서드를 호출하여 이벤트 핸들러를 제거하는 것은 가능하지만 등록 후 바로 제거하기 때문에 해당 이벤트 핸들러는 한번만 호출될 것이다.
	const button = document.querySelector('button');

	button.addEventListener('click', function foo() {
      console.log('Hi yeojin'));
      button.removeEventListener('click', foo);
    });
	
  • 함수 자신을 가리키는 arguments.callee 를 사용할 수 있지만 코드 최적화를 방해하므로 strict mode 에서 사용이 금지된다.
	const button = document.querySelector('button');

	button.addEventListener('click', function foo() {
      console.log('Hi yeojin'));
      button.removeEventListener('click', arguments.callee);
    });
	

this

이벤트 핸들러 어트리뷰트 방식에서의 this

<button onclick="handleClick(this)"> </button>
<script>
	function handleClick(foo) {
		console.log(this); // window
  		console.log(foo); // 이벤트를 바인딩한 button 요소
	}
</script>
  • 이벤트 핸들러 내부의 this 는 전역 객체 window 를 가르킨다
    : 이벤트 핸들러 어트리뷰트 값으로 지정한 문자열은 암묵적으로 생성되는 이벤트 핸들러의 문이기 때문에 일반 함수로 호출된다. 일반 함수로서 호출되는 함수 내부의 this 는 전역 객체 즉 window 를 가르킨다
  • 이벤트 핸들러 호출 시 인수로 전달한 this 는 이벤트를 바인딩한 DOM 요소를 가리킨다.

이벤트 핸들러 프로터티 방식, addEventListener 메서드 방식에서의 this

  • 이벤트 핸들러 내부의 this 는 이벤트를 바인딩한 DOM 요소 (= e.currentTarget) 를 가리킨다.
	const button = document.querySelector('button');

	button.addEventListener('click', function foo(e) {
      console.log(this);
      console.log(e.currentTarget); // button
      console.log(this === e.currentTarget); // true
    });
  • 단 화살표 함수로 정의한 이벤트 핸들러 내부의 this 는 상위 스코프의 this 를 가리킨다.
	const button = document.querySelector('button');

	button.addEventListener('click', e => {
      console.log(this); // window
      console.log(e.currentTarget); // button
      console.log(this === e.currentTarget); // false
    });
  • 클래스에서 이벤트 핸들러를 바인딩 하는 경우
// Bad
class App {
	constructor() {
        this.count = 0;
    	this.button = document.querySelector('button');
      	// increase 메서드를 이벤트 핸들러로 등록
      	this.button.onclick = this.increase;
    }
  
  	increase() {
        // 이벤트 핸들러 내부의 this 는 클래스가 생성할 인스턴스가 아니라 DOM 요소를 가리키기 때문에
      	// this.button 은 this.button.button 이 된다.
      	this.button.textContent = ++this.count;
    }
}
// Good
class App {
	constructor() {
        this.count = 0;
    	this.button = document.querySelector('button');
      	// bind 메서드를 사용해 this 를 전달하여 메서드 내부 this 가 클래스가 생성할 인스턴스를 가리키도록 해야한다.
      	this.button.onclick = this.increase.bind(this);
    }
  
  	increase() {
      	this.button.textContent = ++this.count;
    }
}
// Good
class App {
	constructor() {
        this.count = 0;
    	this.button = document.querySelector('button');
      	// increase 메서드를 이벤트 핸들러로 등록
      	this.button.onclick = this.increase;
    }
  	// 클래스 필드에 할당한 화살표 함수를 이벤트 핸들러로 등록하여 이벤트 핸들러 내부의 this 가 인스턴스를 가리키도록 한다.
  	// 프로토타입 메서드가 아닌 인스턴스 메서드가 된다.
  	increase =() => this.button.textContent = ++this.count;
}
profile
프론트가 좋아요

0개의 댓글