JS 이벤트

gang_shik·2025년 3월 19일
0

JavaScript

목록 보기
20/23

이벤트 전파

  • 만약 비디오 태그를 클릭하게 된 경우, 이벤트 버블링과 이벤트 캡처링이 나타남
  • 이 때 이벤트 버블링의 경우, video 태그를 클릭했어도 그 상위 태그의 해당 이벤트를 전파하는 것을 말함
  • 이벤트 캡처링의 경우, html, body 등 상위 태그의 이벤트를 클릭 했을 때 하위 태그들에 이벤트가 전파되는 것을 의미함
  • 이렇게 이벤트를 디스패칭 하는 것을 이벤트 전파라고 함
  • JS 이벤트는 전파됨을 알고, 위에서 아래로 내려가는 캡쳐링, 아래에서 위로 가는 버블링이 있음을 이해하면 됨

이벤트 버블링

  • 이벤트 버블링은 계속 상위 요소로 이벤트가 전파되는 것을 의미함
<article>
  ARTICLE
  <div>
    DIV
    <p>
      P
      <br />
      <button>BUTTON</button>
    </p>
  </div>
</article>
const article = document.querySelector('article');
const division = document.querySelector('div');
const paragraph = document.querySelector('p');
const button = document.querySelector('button');

// alert 이벤트가 계속 거슬러 올라가서 순차적으로 나옴
article.addEventListener('click', () => window.alert('article'));
division.addEventListener('click', () => window.alert('division'));
paragraph.addEventListener('click', () => window.alert('paragraph'));
button.addEventListener('click', () => window.alert('button'));
  • 위의 경우 계속 이벤트 버블링이 생기는 것, 이를 아래와 같이 stopPropagation을 하면 상위 요소로 이벤트 버블링이 일어나지 않음
const article = document.querySelector('article');
const division = document.querySelector('div');
const paragraph = document.querySelector('p');
const button = document.querySelector('button');

// alert 이벤트가 계속 거슬러 올라가서 순차적으로 나옴
article.addEventListener('click', () => window.alert('article'));
division.addEventListener('click', () => window.alert('division'));
paragraph.addEventListener('click', () => window.alert('paragraph'));
// 상위 태그의 이벤트 버블링이 일어나지 않음, 하지만 다른 요소는 동일하게 버블링이 일어남
button.addEventListener('click', (event) => {
  event.stopPropagation();
  window.alert('button');
});
  • 다른 프레임워크 사용에서도 이벤트 버블링이 나타나기 떄문에 수정을 해야하는 케이스가 충분히 나올 수 있음

이벤트 캡처링

  • 이벤트 캡처링은 상위에서 하위 요소로 내려가는 전파 방법임, 예시를 통해 볼 수 있음
<div>
  <h1>장바구니</h1>
  <ul>
    <li>노트북</li>
    <li>모니터</li>
    <li>책상</li>
    <li>의자</li>
    <li>키보드</li>
    <li>마우스</li>
  </ul>
</div>
const container = document.querySelector('div');
const list = document.querySelector('ul');

container.addEventListener('click', (event) => {
  console.error('div에 이벤트 발생');
  event.target.style.background = 'blue';
});

list.addEventListener('cilck', (event) => {
  console.warn('UL 이벤트 발생');
  event.target.style.background = 'red';
});
  • DIV 태그 클릭시에도 UL에도 영향을 끼침, 이에 대해서 event 인자를 통해서 캡처링을 조절할 수 있음, 캡처링 여부를 넘기는 것
const container = document.querySelector('div');
const list = document.querySelector('ul');

// 이벤트 캡처링을 true로 함으로써 먼저 div 이벤트를 발생시키고나서 ul이 처리됨(기본값은 false)
container.addEventListener(
  'click',
  (event) => {
    console.error('div에 이벤트 발생');
    event.target.style.background = 'blue';
  },
  true,
);

list.addEventListener('cilck', (event) => {
  console.warn('UL 이벤트 발생');
  event.target.style.background = 'red';
});

// 객체로도 처리가능(다양한 옵션이 있음)
container.addEventListener(
  'click',
  (event) => {
    console.error('div에 이벤트 발생');
    event.target.style.background = 'blue';
  },
  {
    capture: true,
    once: true,
    passive: true
  },
);
  • 내 예상과 다르게 이벤트가 발생되고 처리하는 상황에 대해서 이벤트 캡처링과 버블링으로 인해서 일어나는 것일 수 있음

이벤트 위임

  • 아래와 같이 이벤트를 달 때 반복문을 통해서 이벤트를 일일이 다는 경우가 있음
<div>
  <h1>장바구니</h1>
  <ul>
    <li>노트북</li>
    <li>모니터</li>
    <li>책상</li>
    <li>의자</li>
    <li>키보드</li>
    <li>마우스</li>
  </ul>
</div>
const items = document.querySelectorAll('li');
items.forEach((item) => item.addEventListener('click', console.log));
  • 하지만 위와 같이 처리하게 되면 li 태그가 늘어나는만큼 이벤트도 늘어나고 그만큼 메모리도 더 차지하고 비효율적임, 이럴 때 이벤트 위임을 쓸 수 있음
  • 위의 예시의 경우 li 태그 상위 태그가 ul이므로 ul 태그에 일단 이벤트를 주는 것임, 그렇게 해도 하위 태그까지 이벤트가 전파가 되기 때문에 문제가 없음
const items = document.querySelectorAll('ul');
items.addEventListener('click', console.log);
  • 그 외에 class를 등록하는 방법도 있음, 혹은 event의 target의 nodename을 지정해서 할 수도 있음
  • 이 경우 ul 태그 내부의 이벤트에 li도 있기에 그렇게 nodename을 지정해서 할 수 있는 것임
const items = document.querySelectorAll('ul');
items.addEventListener('click', (event) => {
  if (event.target.nodeName === 'LI') {
    alert('LI Click');
  } else {
    alert('No LI Click');
  }
});

// 아예 early return을 할 수도 있음
item.addEventListener('click', (event) => {
  if (event.target.nodeName !== 'LI') {
    return;
  }

  event.target.style.background = 'blue';
});
  • 하나의 이벤트를 아주 쉽게 재활용을 할 수 있음(target을 활용해서)
  • 그리고 클래스명으로 분기를 할수도 있음, 이 때 특정 클래스명이 있는지 확인해서 처리할수도 있음
<div>
  <h1>장바구니</h1>
  <ul>
    <li class="item">노트북</li>
    <li class="item">모니터</li>
    <li class="item">책상</li>
    <li class="item">의자</li>
    <li class="item">키보드</li>
    <li class="item">마우스</li>
  </ul>
</div>
// item이 있는 경우만 실행
item.addEventListener('click', (event) => {
  const [item] = event.target.classList;

  if (item === 'item') {
    event.target.style.background = 'blue';
  }
});
  • 여러개 요소의 일일이 이벤트를 등록하지 않고 이벤트를 위임해서 관리하기 수월하게 쓸 수 있음

debounce

  • 브라우저에서 검색창 입력할때마다 주기적으로 키 입력시 계속해서 서버를 호출함, 그나마 현재 상용브라우저는 잘 처리되어 있는 것임
  • 아래 예시로 보면 input에 값을 입력할 때마다 이벤트가 호출이 계속됨, 이를 API 호출이라고 하면 자원 낭비가 아주 심한 것임, 이럴 때 debounce 처리를 해 볼 수 있음
<form>
  <input type="text"/>
</form>
let i = 0;

document.querySelector('input').addEventListener('keyup', () => {
  i = i + 1;
  console.log(i);
});
// 마지막 호출 이후 일정 밀리세컨드 이후로 지연된 호출을 하도록 debounce된 함수
// 실행시킬 함수, 지연시킬 밀리세컨드를 인자로 받음
function debounce(callback, wait) {
  let timeout; // 초기 undefined

  // 함수가 호출시 매번 반환
  return function (...args) {
    // 상위에서 this를 받아서 callback의 인자로 받아서 넘김
    const context = this;

    // 타이머를 취소 시키는 메소드
    clearTimeout(timeout);
    timeout = setTimeout(() => callback.apply(context, args), wait);
  };
}

// 앞서 쓴 이벤트를 debounce로 감싸서 처리
document.querySelector('input').addEventListener(
  'keyup',
  // debouce의 함수 호출과 시간을 보냄
  debounce(() => {
    i = i + 1;
    console.log(i);
  }, 1000),
);
  • 위처럼 처리하면 입력한 시간대로 이벤트 처리를 함, 그래서 불필요한 호출을 상당히 개선하고 최적화를 할 수 있음
  • debounce는 이벤트 발생이 많을 때 가장 마지막 이벤트만을 실행시킴, 이 실행시키는 시점은 밀리세컨드로 좌우하는 것임
  • 언제 사용해야 할 지 생각해봐야함

throttle

  • 과거에는 싱글 페이지 어플리케이션이 없을 땐, 페이징 형태의 UI가 많았음, 보조적인 페이지 혹은 더보기 버튼으로 필요한 내용을 더 보는 것
  • 하지만 요즘에는, 무한 스크롤해서 계속 아이템이 등장하는 로직과 매커니즘이 생겨남
  • 이럴 때 throttle을 쓰면 좋음
<div style="background-color: red"><h2>1</h2></div>
<div style="background-color: orange"><h2>2</h2></div>
<div style="background-color: yellow"><h2>3</h2></div>
<div style="background-color: green"><h2>4</h2></div>
<div style="background-color: blue"><h2>5</h2></div>
<div style="background-color: navy"><h2>6</h2></div>
<div style="background-color: violet"><h2>7</h2></div>
  • 이벤트 처리는 유사하나 window에 scroll을 달았음, 아래의 경우 스크롤 할 때마다 계산을 함 이를 최적화하는데 throttle이 유용함
let i = 0;

window.addEventListener('scroll', () => {
  i = i + 1;
  console.log(i);
});
  • 아래의 throttle을 사용할 수 있음
// 이벤트 발생이 많을 때, 특정 밀리세컨드동안 이벤트를 차단하고 단 한 번만 실행시킴
// 실행시킬 함수, 차단시킬 밀리세컨드
function throttle(callback, wait) {
  let timeout = null;

  return function (...args) {
    const context = this;

    if (!timeout) {
      timeout = setTimeout(() => {
        callback.apply(this, args);
        timeout = null;
      }, wait);
    }
  };
}

// throttle 사용, 아래 scroll시, 1초에 1번씩만 나오게 됨, 불필요한 사용을 줄임
let i = 0;

window.addEventListener(
  'scroll',
  throttle(() => {
    i = i + 1;
    console.log(i);
  }, 1000),
);
profile
측정할 수 없으면 관리할 수 없고, 관리할 수 없으면 개선시킬 수도 없다

0개의 댓글