fffo

이벤트 본문

Programming/Javascript

이벤트

gggs 2021. 11. 12. 16:14

이벤트 드리븐 프로그래밍

  • 이벤트 : 무언가 일어났다는 신호
  • 이벤트 핸들러 : 이벤트가 발생했을 때 실행되는 함수
  • 이벤트 핸들러 등록 : 브라우저에게 이벤트 핸들러의 호출을 위임
  • 브라우저가 행동을 감지해 이벤트를 발생 시킴
  • 이벤트 드리븐 프로그래밍 : 이벤트 중심으로 프로그램의 흐름을 제어하는 프로그래밍 방식

이벤트 타입

  • 이벤트 타입 : 이벤트의 종류를 나타내는 문자열

이벤트 핸들러 등록

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

  • HTML 요소의 이벤트 핸들러 어트리뷰트에 값으로 문(statement)을 할당
  • 각 어트리뷰트 이름은 'on + 이벤트 타입'
<button onclick="sayHi('dlwlrma')">Click me</button>
<script>
    function sayHi(name) { console.log(`Hi! ${name}.`); }
</script>
  • 이벤트 핸들러 어트리뷰트 값은 암묵적으로 생성될 이벤트 핸들러의 몸체를 의미함
  • 위 예에서 onclick="sayHi('dlwlrma')" 어트리뷰트는 파싱되어 다음과 같은 함수를 암묵적으로 생성 함
function onclick(event) { sayHi('dlwlrma'); }
  • 생성한 함수는 이벤트 핸들러 어트리뷰트 이름과 동일한 키인 onclick 이벤트 핸들러 프로퍼티에 할당 함
  • 함수의 참조를 할당하는 것이 아닌 함수 호출문을 할당하는 이유는 이벤트 핸들러에 인수를 전달하기 위함

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

  • window 객체, Document, HTMLElement 타입의 DOM 노드 객체는 이벤트에 대응하는 이벤트 핸들러 프로퍼티를 가짐
  • 각 프로퍼티 키는 이벤트 핸들러 어트리뷰트의 이름과 같이 on + 이벤트타입
  • 이벤트 핸들러 프로퍼티에 함수를 바인딩해서 이벤트 핸들러 등록
<button>Click me</button>
<script>
    const $button = document.querySelector('button');
    $button.onclick = function () { console.log('clicked'); };
</script>
  • 결과적으로 이벤트 핸들러 어트리뷰트와 동일하지만 하나의 프로퍼티에 두 개 이상의 이벤트 핸들러 등록을 하지 못하는 단점이 있음
  • 어트리뷰트에 두 번 이상 핸들러를 등록 시 핸들러가 덮어 씌워짐

addEventListener 메서드 방식

  • DOM Level 2에서 도입된 EventTarget.prototype.addEventListener 메서드를 이용해 핸들러 등록
  • 앞선 두 방식은 DOM Level 0부터 제공되던 방식

[EventTarget].addEventListener(['eventType'], [functionName] [, options]);

  • 첫 번째 인자로 전달되는 이벤트 타입에는 접두사 on이 붙지 않음
  • 마지막 인자는 다음의 프로퍼티를 갖는 객체
    • once : true이면 이벤트가 트리거 될 때 리스너가 자동으로 삭제됨
    • capture :true는 캡처링 당계에서 이벤트를 캐치, false는 버블링 단계에서 이벤트를 캐치
    • passive : true면 핸들러가 preventDefault를 호출하지 않음
    • option 객체가 아닌 false/true로 할당 시 capture의 값으로 지정됨
  • addEventListener 메서드 방식은 프로퍼티에 바인딩된 이벤트 핸들러에 아무 영향을 주지 않음
  • addEventListener 메서드 방식과 프로퍼티 바인딩 형식을 모두 사용해서 같은 이벤트에 핸들러 등록 시 두 핸들러 모두 작동함
  • addEventListener 메서드는 하나 이상의 핸들러 등록 가능, 등록된 순서대로 호출됨
  • 단, 참조가 동일한 핸들러를 중복 등록 시 하나만 등록 됨
<button>Click me</button>
<script>
    const $button = document.querySelector('button');
    $button.addEventListener('click', function () {
        console.log('clicked');
    });
</script>

이벤트 핸들러 제거

  • addEventListener로 등록한 핸들러는 removeEventListener 메서드를 사용해 제거 가능
  • removeEventListener에 전달한 인수가 addEventListener로 등록했던 인수와 동일하면 제거됨
  • 인수가 동일해야 하기 때문에 무명 함수로 핸들러 등록 시 제거 할 수 없음
  • 프로퍼티 방식으로 등록한 핸들러는 removeEventListener로 제거 불가
    • 프로퍼티에 null을 할당해 제거

이벤트 객체

  • 이벤트 발생 시 이벤트 정보를 담은 이벤트 객체가 동적으로 생성됨
  • 생성된 이벤트 객체는 이벤트 핸들러의 첫 번째 인수로 전달됨
  • 어트리뷰트 방식으로 이벤트 객체를 전달 받으려면 이벤트 핸들러의 첫 번째 매개변수 이름이 반드시 event여야 함
  • 이벤트 객체도 생성자 함수에 의해 생성되어 프로토타입 체인의 일원이므로 상위 객체 프로토타입 프로퍼티를 상속받음

이벤트 전파

  • DOM 요소 노드에서 발생한 이벤트는 DOM 트리를 통해 전파됨
  • 캡처링 : 상위 → 하위 방향으로 전파
  • 버블링 : 하위 → 상위 방향으로 전파
  • 이벤트 전파로 상위 DOM요소에서 이벤트 캐치 가능
  • 버블링을 통해 전파되지 않는 이벤트들이 있지만 대체할 수 있는 이벤트가 존재하므로 캡처링 단계에서 이벤트를 캐치해야 할 경우는 거의 없음
  • 버블링 안 됨 / 버블링 됨(대체)mouseleave / mouseoutblur / focusout
  • focus / focusin
  • mouseenter / mouseover

이벤트 위임

  • 상위 요소에 이벤트를 위임함으로써 이벤트 핸들러 등록을 효율적으로 관리 가능
<!DOCTYPE html>
<html>

<head>
  <title>bbubbu</title>
      <meta charset="UTF-8" />
      <style>
        .active {
          color: red;
        }
      </style>
</head>

<body>
  <ul id="fruits">
    <li id="apple" class="active">Apple</li>
    <li id="banana">Banana</li>
    <li id="orange">Orange</li>
  </ul>
  <p>선택된 아이럼: <em class="msg">apple</em></p>
  <script>
    const $fruits = document.querySelector('#fruits');
    const $msg = document.querySelector('.msg');

    function activate({ target }) {
      if (!target.matches('#fruits > li')) return;
      [...$fruits.children].forEach($fruit => {
        $fruit.classList.toggle('active', $fruit === target);
        $msg.textContent = target.id;
      });
    }
    $fruits.addEventListener('click', activate);
  </script>
</body>

</html>
  • 이벤트 객체의 currentTarget 프로퍼티는 핸들러가 바인딩된 DOM 요소를 가리키고 target 프로퍼티는 이벤트를 발생시킨 DOM요소를 가리킴

DOM요소의 기본 동작 조작

기본 동작 중단

  • 이벤트 객체의 preventDefault 메서드는 DOM 요소의 기본 동작을 중단 시킴

이벤트 전파 방지

  • stopPropagation

이벤트 핸들러 내부의 this

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

  • 어트리뷰트 방식은 암묵적으로 새로운 함수가 만들어지고 그 함수의 몸체에 핸들러의 호출문이 들어가는 형식이므로 this는 중첩함수, 즉 일반 함수로써의 호출이 되어 전역객체가 할당됨
  • 단, 인수로 this를 전달하면 새로 만든 함수 몸체에서의 this가 되므로 이 함수의 this가 됨. 이는 프로퍼티 메서드로 할당되는 함수이므로 this는 이벤트를 바인딩한 DOM요소를 가리킴

이벤트 핸들러 프로퍼티 방식과 addEventListener 메서드 방식

  • 둘 모두 this는 이벤트를 바인딩한 DOM요소를 가리킴
  • 단, 화살표 함수로 정의한 이벤트 핸들러 내부의 this는 상위 스코프의 this를 가리킴에 유의
  • 클래스에서 이벤트 핸들러를 바인딩할 때 this에 주의해야 함
<!DOCTYPE html>
<html>

<head>
  <title>document</title>
      <meta charset="UTF-8" />
</head>

<body>
  <button class="btn">0</button>  
  <script>
    class App {
      constructor() {
        this.$button = document.querySelector('.btn');
        this.count = 0;
        this.$button.onclick = this.increase.bind(this);
      }
      increase() {
        this.$button.textContent = ++this.count;
      }
    }
    new App();
  </script>
</body>

</html>
  • 클래스 필드에 할당한 화살표 함수를 핸들러로 등록해 핸들러 내부의 this가 인스턴스를 가리키도록 할 수 있지만 이 때 핸들러는 프로토타입 메서드가 아닌 인스턴스 메서드가 됨

이벤트 핸들러에 인수 전달

  • 인수 전달은 함수 호출 시에 가능함
  • 따라서 어트리뷰트 방식을 제외한 프로퍼티 방식, addEventListener 메서드 방식은 브라우저가 호출하기 때문에 인수를 전달할 수 없음
  • 그러나 핸들러 내부에서 함수를 호출해 인수를 전달하거나 핸들러를 반환하는 함수를 호출해 인수를 전달 가능
<!DOCTYPE html>
<html>

<head>
  <title>document</title>
  <meta charset="UTF-8" />
</head>

<body>
  <label>User name <input type="text"></label>
  <em class="message"></em>
  <script>
    const MIN_USER_NAME_LENGTH = 5;
    const $input = document.querySelector('input[type=text]');
    const $msg = document.querySelector('.message');

    const checkUserNameLength = min => {
      $msg.textContent = $input.value.length < min ? `이름은 ${min}자 이상 입력해주세용` : '';
    };
    $input.onblur = () => {
      checkUserNameLength(MIN_USER_NAME_LENGTH);
    }
  </script>
</body>

</html>

커스텀 이벤트

커스텀 이벤트 생성

  • 이벤트 객체는 이벤트 생성자 함수로 생성 가능
  • 생성자 함수로 명시적으로 생성한 이벤트 객체는 임의의 이벤트 타입을 지정 가능
Comments