이벤트
여기서 알고 있어야 하는 것은 이벤트 핸들러 함수는 비동기적으로 동작한다는 것입니다. 이미 콜스택에 남아있는 실행 컨택스트가 없는 상태에서 어떤 이벤트가 발생했을 때 함수가 실행된다는 것이니까요. 그렇기 때문에 핸들러 내부에서 특정 값에 변화를 주어도 화면에 반영이 되...
이벤트
브라우저에서 어떤 일이 발생하게 되면 이벤트를 발생시키는 경우가 있습니다. 대표적으로 클릭이나 키보드 입력과 같은 경우죠. 우리가 웹 프로그래밍을 하게 되면 이런 이벤트가 발생했을 때 어떤 행동을 하게 하고 싶은 경우가 자주 있습니다. 버튼을 눌렀을 때 배경색이 변하게 하는 경우가 그 예죠. 이렇게 이벤트가 발생할 때 어떤 행동을 하게 하기 위해선 함수를 이용합니다. 그 함수를 보통 이벤트 핸들러라고 말합니다.
이벤트 핸들러 동작
이벤트 핸들러는 우리가 작성할 수 있지만, 원하는 시점에 호출하는 것이 불가능합니다. 왜냐면 그때는 이미 사용자가 브라우저를 보고 있는 상태이기 때문이죠. 이미 코드가 엔진에 의해 읽혀 렌더링도 완료된 상태인 것입니다. 그렇기 때문에 우리는 이 핸들러 함수를 호출하는 것을 브라우저에게 넘겨줍니다. 즉, 이벤트가 발생하면 핸들러 함수를 브라우저가 알아서 호출하게 되는 것이죠.
여기서 알고 있어야 하는 것은 이벤트 핸들러 함수는 비동기적으로 동작한다는 것입니다. 이미 콜스택에 남아있는 실행 컨택스트가 없는 상태에서 어떤 이벤트가 발생했을 때 함수가 실행된다는 것이니까요. 그렇기 때문에 핸들러 내부에서 특정 값에 변화를 주어도 화면에 반영이 되지 않는 경우도 종종 볼 수 있습니다. 화면에 어떤 값을 넣는 코드는 이미 실행되고 종료되었기 때문이죠. 이런 경우엔 핸들러 내부에서 그 코드를 다시 실행시키는 방식으로 처리를 하게 될 것입니다.
이벤트 핸들러 등록
그렇다면 그 이벤트 핸들러를 어떻게 브라우저에게 전달해 줄 수 있을까요? 그 방법은 크게 3가지 입니다.
이벤트 핸들러 어트리뷰트 방식
어트리뷰트라는 말은 오랜만에 들으실 수 있습니다. 바로 HTML에서 속성을 나타내는 말이죠.
즉, HTML에서 이벤트 핸들러를 속성으로 넣을 수 있다는 소리입니다.
<button onclick = 'hello()'>button</button>
<script>
function hello(){
console.log('hello');
}
</script>
위와 같은 방식으로 이벤트 핸들러를 만들 수 있습니다. 생성된 버튼을 눌렀을 때, hello함수가 실행되는 것이죠. 여기서 주의할 점은 함수의 참조가 아닌 호출문을 넣어줘야 한다는 것입니다. 그 이유는 위의 코드에서 이벤트 핸들러 함수의 내부를 ''의 내용으로 넣는다고 생각해야 되기 때문이죠.
// onclick = 'hello()'
function (){
hello();
}
위와 같은 방식의 함수를 만들어 핸들러 함수로 사용하는 것입니다. 만약 호출문이 아닌 참조를 넣게 되면, 문자열로 인식을 하게 될 것입니다.
이벤트 핸들러 프로퍼티 방식
이번에는 프로퍼티 방식입니다. 프로퍼티는 객체의 속성을 이야기하죠. 맞습니다. 자바스크립트에서 객체의 속성으로 이벤트 핸들러를 할당하는 방식을 말하는 것입니다.
const $ = sel => document.querySelector(sel);
const $btn = $('button');
$btn.onclick = function(){
console.log('click');
}
버튼을 querySelector로 가져와 클릭 이벤트 핸들러를 등록한 것입니다. 여기서 이벤트를 달아주는 요소를 이벤트 타겟이라고 하고, 프로퍼티를 on + 이벤트 타입을 이용해서 작성합니다. 그 후 핸들러가 될 함수를 할당해주면 됩니다.
주의할 것은 이 방식으로는 하나의 이벤트 핸들러만 달아줄 수 있다는 것이죠. 왜 그럴까요? 단순히 생각하면 프로퍼티에 특정 값을 할당하는 것이니까 나중에 할당하는 값이 마지막에 덮어 씌워지겠죠?
addEventListener 방식
DOM LEVEL2에서 도입된 방식입니다.
EventTarget.addEventListener('event type', callback, [, useCapture]) 방식으로 사용하면 됩니다. 이벤트 타겟은 querySelector나 createElement로 만든 요소가 되겠죠. 이벤트 타입은 문자열 형식으로 첫번째 인자로 넣어줍니다. 이후 콜백 함수를 전달해 해당 이벤트가 발생하면 콜백 함수가 실행되는 방식이죠.
const $ = sel => document.querySelector(sel);
const $btn = $('button');
// $btn.onclick = function(){
// console.log('click');
// }
$btn.addEventListener('click',() => {
console.log('clicked');
});
위 방식처럼 사용하면 됩니다. 굉장히 간단하죠? 달아둔 이벤트를 삭제하고 싶은 경우는 removeEventListener를 사용합니다. 단 이때는 함수가 변수에 들어있는 경우에만 가능합니다.
const onClick = () => {
console.log('clicked');
}
$btn.addEventListener('click', onClick);
$btn.removeEventListener(onclick);
이벤트 객체
이벤트 발생 후 관련 정보가 담긴 이벤트 객체가 콜백함수의 첫 인자로 자동으로 들어가게 됩니다.
const onClick = (e) => {
console.log(e); // 이벤트 객체를 볼 수 있습니다.
}
$btn.addEventListener('click', onClick);
이벤트 객체는 공통된 프로퍼티를 가지고 있습니다.
- type : 이벤트 타입
- target : 이벤트가 발생한
DOM요소 - currentTarget : 이벤트 핸들러가 달린
DOM요소 - eventPhage : 이벤트 전파 단계
- bubbles : 버블링 전파 여부
- cancelable : preventDefault를 이용해 기본 동작을 취소할 수 있는지 여부 등
이벤트 전파
이벤트가 발생하면 DOM트리를 따라 전파가 됩니다.
이벤트가 발생한 요소를 기준으로 전파가 이루어지는데요. 크게 3단계로 나눌 수 있습니다.
그림처럼 캡쳐링 단계, 타겟 단계, 버블링 단계를 확인할 수 있죠.
이런 이벤트 전파로 인해 상위 요소도 이벤트를 인지할 수 있게 됩니다. 단, 이벤트 버블링을 통해 전파되지 않는 이벤트도 존재합니다.
이벤트 위임
li태그를 이용해서 리스트 형식으로 버튼을 만든다고 가정을 합시다. 만약, 모든 li태그의 버튼에 이벤트 리스너를 달아주게 되면, 같은 동작을 하는 함수가 많이 생성되는 것과 똑같은 경우가 될 것입니다.
const $c = sel => document.createElement(sel);
const $ul = $c('ul');
const data = [1,2,3,4,5]
for(const a of data){
const $li = $c('li');
const $btn = $c('button');
$btn.addEventListener('click',() => {
console.log('button clicked');
});
$li.append($btn);
$ul.append($li);
}
이는 성능을 떨어뜨리는 원인이 될 수 있죠. 이럴 때는 이벤트 전파를 이용해 해결할 수 있습니다. 상위 DOM요소에서 이벤트를 인지할 수 있기 때문에 특정 이벤트를 감지할 최상위 요소에 이벤트 리스너를 달아주는 방식이죠. 그 후 event.target이 우리가 원하는 요소일 때 특정 행동을 하도록 작성하면 될 것입니다.
const $c = sel => document.createElement(sel);
const $ul = $c('ul');
const data = [1,2,3,4,5];
$ul.addEventListener('click',(e) =>{
if(e.target.tagName === 'BUTTON'){
console.log('button clicked');
}
});
for(const a of data){
const $li = $c('li');
const $btn = $c('button');
$li.append($btn);
$ul.append($li);
}
위와 같은 방식으로 이벤트를 다루는 것이 메모리 상에서 훨씬 효율적일 것입니다.
만약 이벤트 핸들러 함수에서 인자를 받고 싶다면 커링 방식을 이용하면 됩니다.
const onClick = name => e => console.log(name);
$btn.addEventListener('click',onClick('crowwan'));
위의 경우 onClick이 호출되면 함수를 반환하기 때문에 이벤트 핸들러 함수로 동작이 가능합니다.