1. 이벤트 핸들러 등록하기
HTML의 속성이나 DOM 프로퍼티를 활용해 이벤트를 등록하는 방법 외에 Element.addEventListener('type', 'handler')를 통해서 이벤트 핸들러를 등록할 수 있다. 이 방법이 이벤트 핸들러를 등록할 때 가장 권장되는 방법이다. 이렇게 하면 하나의 요소에 여러개의 독립적인 이벤트 핸들러를 등록할 수가 있다.
<body>
<div id="content" class="btns">
<button id="myBtn">JS Click!</button>
<button onclick="console.log('Hello Codeit!')">HTML Click!</button>
</div>
<script src="index.js"></script>
</body>
// 이벤트 등록하기
let btn = document.querySelector('#myBtn');
// btn.onclick = function () {
// console.log('Hi Code World!');
// };
// 이렇게 dom요소에 접근에 onclick 프로퍼티를 활용하는 방법 같은 이벤트 핸들러는 한가지 문제를 가지고 있다.
// innerHTML이나 className같은 프로퍼티를 활용할 때도 프로퍼티에 어떤 값을 할당하는 방식은
// 기존에 있던 값을 덮어쓰는 형태로 동작하기 때문에 기존에 값을 유지하면서 일부만 수정하기는 어려운 단점이 있었다.
// onclick 프로퍼티도 새로 쓰면 기존의 값을 덮어쓴다.
function event1() {
console.log('Hi Codeit!');
}
function event2() {
console.log('Hi again!');
}
//위에 코드를 실행시킨다면 Hi Codeit!은 나오지 않고 Hi again!만 출력된다.
//지금은 간단한 코드이지만 각 이벤트 별로 중요한 리턴 값이 있을 경우에 그 리턴 값도 다뤄야 하는 문제가 생긴다.
// elem.addEventListener(event, handler)
btn.addEventListener('click', event1);
btn.addEventListener('click', event2);
Element.addEventListener('event', 'handler')는 이벤트 타입을 문자열로 첫번째 파라미터에 입력하고, 두번째 파라미터에는 이벤트 핸들러를 전달하면 된다. 메소드를 여러번 호출하게 되도 하나의 요소에 여러개의 독립적인 이벤트 핸들러를 등록할 수 있다.
실행해보면 이벤트1과 이벤트2가 동시에 실행되는것을 확인 할 수 있다.
2. 이벤트 핸들러 삭제하기
addEventListener 메소드를 활용해서 이벤트 핸들러를 등록했다면, Element.removeEventListner('type', 'handler')를 통해서 이벤트 핸들러를 삭제할 수 있다. 등록할 때와 똑같이 파라미터를 전달하면 해당되는 이벤트 핸들러가 삭제되는 방식이다.
단, 이벤트를 삭제할 땐 반드시 이벤트를 등록할 때 외부에 함수를 만들어서 해당 함수의 이름으로 핸들러를 전달해줘야 한다.
// elem.removeEventListener(event, handler)
btn.removeEventListener('click', event2);
btn.addEventListener('click', function() {
console.log('event3!');
});
btn.removeEventListener('click', function() {
console.log('event3!');
});
//이렇게는 생성도, 삭제도 되지 않는다.
function event3() {
console.log('Hi event3!');
};
//이렇게 변수에 담아서
btn.addEventListener('click', event3);
//사용을 해주어야 한다
btn.removeEventListener('click', event3);
3. 이벤트 객체 (Event Object)
웹페이지에서 이벤트가 발생하면 관련된 정보를 담은 이벤트 객체가 만들어지고 이벤트 핸들러의 첫번째 파라미터로 전달된다. 이벤트 객체는 이벤트 종류마다 가지고 있는 프로퍼티가 다르고, 이벤트에 대한 유용한 정보들을 프로퍼티로 가지고 있다.
<body>
<div id="content" class="btns">
<input type="text" id="myInput" placeholder="type anything">
<button id="myBtn">click me!</button>
</div>
<script src="index.js"></script>
</body>
// 이벤트 객체 (Event Object)
const myInput = document.querySelector('#myInput');
const myBtn = document.querySelector('#myBtn');
function printEvent(event) { //(event) 이벤트 핸들러의 첫번째 파라미터
console.log(event);
event.target.style.color = 'red';
}
myInput.addEventListener('keydown', printEvent);
myBtn.addEventListener('click', printEvent);
특히나 target은 DOM 요소가 담겨 있기 때문에 event.target.style.color = 'red'; 이런식으로 이벤트가 발생했을 때 해당 요소를 수정한다거나 혹은 그 요소가 가지고 있는 속성 값들을 참조해야 되는 상황에서 유용하게 활용된다.
프로퍼티 | 설명 |
type | 이벤트 이름 (click, mouseup, keydown 등) |
target | 이벤트가 발생한 요소 |
currentTarget | 이벤트 핸들러가 등록된 요소 |
timeStamp | 이벤트 발생 시각(페이지가 로드된 이후부터 경과한 밀리초) |
bubbles | 버블링 단계인지를 판단하는 값 |
4. 이벤트 버블링 (Event Bubbling)
이벤트는 전파가 된다. 어떤 요소에서 이벤트가 발생하면 해당 요소에 등록된 이벤트 핸들러가 동작하는 것뿐만 아니라 (같은 타입의 이벤트에 한해서) 부모 요소로 이벤트가 계속해서 전파되면서 각 요소에도 등록된 이벤트 핸들러가 있다면 차례로 이벤트 핸들러가 동작한다.(최상단의 윈도우 객체를 만날때까지 반복) 자식 요소에서 부모 요소로 이벤트가 전파되는 것을 이벤트 버블링(Event Bubbling)이라고 한다.
이벤트 버블링이 일어나도 이벤트 객체의 target 프로퍼티는 변하지 않고 처음 이벤트가 발생한 시작점을 담고 있다.
참고로 이벤트 버블링은 이벤트 객체의 stopPropagation 메소드로 전파를 막을 수 있다.
for (let item of items) {
item.addEventListener('click', function(e) {
console.log('item Event');
console.log(e.currentTarget);
e.stopPropagation();
});
};
//이 코드를 쭉 살펴보자면 items에 있는 item을 파라미터로 담고, 반복문을 실행한다.
//item에 클릭했을 때 이벤트 리스너 프로퍼티를 부여한다. 어떤 함수가 실행될거냐면
//console.log에 item Event라고 나오고, console.log에 이벤트 핸들러가 등록된 요소를 출력해주고
//버블링을 멈춰줘
위 코드를 위에서부터 해석해보자면,
for of 문으로 반복문을 실행할건데 items 함수에 담긴 item, 각 li 요소를 말한다. item이 클릭되는 이벤트가 발생하면 함수를 실행해주세요, 어떤 함수? console에 'item Event'라는 문자열 출력해주시고, console에 e.currentTarget 이벤트 리스너가 연결된 (핸들러가 등록된) 요소(즉, 클릭된 li 요소)를 출력해주세요. 그리고 나서 이벤트가 부모요소로 전파되는 버블링을 멈춰주세요.
메소드 하나로 간단하게 버블링을 막을 수는 있지만 정말 필요한 경우가 아니라면 가급적 버블링을 막는 일은 피하는것이 좋다. 지금처럼 아이템 영역에서 이벤트 버블링을 막아버리면 바로 위에 있는 리스트 뿐만 아니라 모드 부모요소 입장에서 아이템 영역만큼의 이벤트를 발생시킬 범위가 사라져버리게 된다.
예를들자면 페이지 전체에 어떤 이벤트를 만들고 싶을 경우 document나 body 같은 문서 전체를 다룰 수 있는 상위 요소의이벤트 핸들러를 만들어 줄텐데 버블링이 막혀있는 구간이 존재하게 되면 당연히 클릭 했을 때 버블링이 막혀버리기 때문에 그 부분만 원하는 이벤트 결과를 얻지 못한다. 버블링을 잘 이해하고 이벤트를 설계한다면 버블링을 막을 일이 많지 않다.
+여담
버블링 외에도 캡쳐링(capturing)이라는 흐름이 존재한다. 실제 코드에서 자주 쓰이진 않는다. 캡처링 단계에서 이벤트를 발생시켜야 하는 일은 매우 드물다.
- 캡처링 단계 : 이벤트가 하위 요소로 전파되는 단계
- 타깃 단계 : 이벤트가 실제 타킷 요소에 전달되는 단계 = 이벤트 객체의 target 프로퍼티가 되는 요소에 등록되어있던 이벤트 핸들러가 동작하는 단계 (가장 처음 이벤트 핸들러가 동작하는 순간)
- 버블링 단계 : 이벤트가 상위 요소로 전파되는 단계
캡처링은 이벤트가 발생하면 가장 먼저, 그리고 버블링의 반대방향으로 진행되는 이벤트 전파 방식이다.
이벤트가 발생하면 가장먼저 window 객체에서부터 target까지 이벤트 전파가 일어나고, (캡쳐링) 타겟에 도달하면 등록된 이벤트 핸들러가 동작하고, (타깃) 이후에 다시 window 객체로 이벤트가 전파된다. (버블링)
자주 사용되진 않지만 그럼에도 불구하고 캡처링 단계에서 이벤트 핸들러를 동작해야 된다면? addEventListener에 세번째 프로퍼티에 true 또는 { capture:true }를 전달하면 된다.
for (let elem of document.querySelectorAll('*')) {
elem.addEventListener("click", e => alert(`캡쳐링 단계: ${elem.tagName}`), true);
elem.addEventListener("click", e => alert(`버블링 단계: ${elem.tagName}`));
}
5. 이벤트 위임 (Event Delegation)
버블링 개념을 활용하면 자식 요소 각각에 이벤트 핸들러를 하나씩 등록할 필요 없이 부모 요소에서 한 번에 자식 요소들에 발생한 이벤트를 관리할 수도 있다.
이렇게 이벤트를 다루는 방식을 자식 요소의 이벤트를 부모 요소에 위임한다고 해서 이벤트 위임(Event Delegation)이라고 한다.
만약 이미 있는 요소들에 이벤트 핸들러를 다 설정했는데 새로운 아이템을 추가 하는 상황이 발생하게 되면 추가된 아이템에는 이벤트 핸들러가 동작하지 않는다. 그래서 매번 추가할 때마다 이벤트 핸들러를 새로 등록해야 되는 문제가 있다. 이럴땐 이벤트 버블링을 활용하자.
리스트에 이벤트 핸들러를 하나밖에 등록하지 않았지만 아이템 각각의 이벤트 핸들러를 등록한것처럼 동작하고, 나중에 추가한 아이템도 이벤트 핸들러가 잘 동작한다. 하지만 그래도 문제가 한가지 있다. 자식요소를 제외한 온전한 부모요소를 클릭해도 이벤트 핸들러가 동작된다. item(li)영역을 벗어난 list(ul)영역을 선택해도 done class가 toggle되어 이벤트가 발생한다. 그래서 이벤트 위임을 할때는 명확하게 원하는 요소에서 의도한 동작이 일어나게끔 따로 처리를 해줘야한다.
// 이벤트 위임 (Event Delegation)
const list = document.querySelector('#list');
list.addEventListener('click', function(e) {
// target프로퍼티의 tagName이 LI인지 확인하거나
// if (e.target.tagName === 'LI')
if (e.target.classList.contains('item')) {
//classList의 contains 메소드를 이용해 item이라는 class를 가지고 있는지 확인하면 해결할 수 있다.
e.target.classList.toggle('done');
}
});
const li = document.createElement('li');
li.classList.add('item');
li.textContent = '일기 쓰기';
list.append(li);
tagName 프로퍼티는 해당 요소의 태그 이름 값을 대문자로 담고 있는 프로퍼티이고, classList의 contains 메소드는 파라미터로 전달하는 값이 해당 요소의 클래스 속성에 있는지 확인해 불린형태로 결과를 리턴해주는 메소드이다.
6. 브라우저의 기본 동작
브라우저에는 각 태그별 혹은 상황별로 기본적으로 약속된 동작들이 있다.
예를 들어 마우스 오른쪽 버튼을 클릭하면 상황에 맞는 메뉴 창이 뜬다거나, input 태그에 커서를 두고 키보드 키를 누르면 해당 값이 입력된다거나 하는. 만약 이런 동작들을 막고 싶다면 이벤트 객체의 preventDefault 메소드를 통해 막을 수가 있다.
<body>
<a id="link" href="https://www.codeit.kr/">Link</a>
<input type="checkbox" id="checkbox">
<input type="text" id="input" placeholder="input">
<p id="text">
I Love Codeit!
</p>
<script src="index.js"></script>
</body>
// 브라우저의 기본 동작
const link = document.querySelector('#link');
const checkbox = document.querySelector('#checkbox');
const input = document.querySelector('#input');
const text = document.querySelector('#text');
//event.preventDefault 브라우저의 기본적인 동작을 막는 메소드
link.addEventListener('click', function(e) {
e.preventDefault(); //링크가 클릭되면 기본동작(href속성 값으로 넘어가는것)이 되지 않고
alert('지금은 이동할 수 없습니다.'); //경고창을 띄워주세요.
});
//키도브 이벤트로 활용해보기
input.addEventListener('keydown', function(e) {
if (!checkbox.checked) { //체크박스가 체크되지 않으면
e.preventDefault(); //input태그에서 키가 입력되지 않게 막아주시고
alert('체크박스를 먼저 체크해 주세요.'); //경고창을 띄워주세요.
}
});
//마우스 우클릭 방지해보기, 마우스 우클릭 = contextmenu 라는 타입으로 이벤트가 발생된다.
text.addEventListener('contextmenu', function(e) {
e.preventDefault(); //다른곳에서는 우클릭이 되지만 텍스트 위에서 우클릭을 할 경우
alert('마우스 오른쪽 클릭은 사용할 수 없습니다.'); //경고창이 띄워진다.
});
//문서 전체에 우클릭 방지를 하고 싶다면 text를 document로 바꿔주면 된다.
하지만 각 HTML 태그들이 가지고 있는 고유한 역할과 의미를 훼손하게 될 수도 있기 때문에 꼭 필요한 경우에만 주의해서 사용해야 한다.
'코린이 개념잡기 > JavaScript' 카테고리의 다른 글
이벤트 자세히 다뤄보기 (1) | 2024.12.15 |
---|---|
어떤 이벤트들이 있을까? (0) | 2024.12.14 |
console.log와 console.dir의 차이 (0) | 2024.12.13 |
객체, 요소, 노드 (1) | 2024.12.13 |
이벤트와 클릭 (0) | 2024.12.13 |