본문 바로가기
개인공부/모던 자바스크립트 Deep Dive

40장 이벤트(3)

by 강물둘기 2023. 2. 1.

* 아래 내용은 이웅모 저자님의  모던 자바스크립트 Deep Dive 책(위키북스)을 정리한 내용입니다.

   저작권에 문제가 된다면 삭제하도록 하겠습니다.

 

 

8. DOM 요소의 기본 동작 조작

DOM 요소는 저마다 기본 동작이 있다.

이벤트 객체의 preventDefault 메서드는 이러한 DOM 요소의 기본 동작을 중단시킨다.

document.querySelector('a').onclick = e => {
  // a 요소의 기본 동작을 중단한다.
  e.preventDefault();
};

 

이벤트 객체의 stopPropagation 메서드는 이벤트 전파를 중지시킨다.

<!DOCTYPE html>
<html>
<body>
  <div class="container">
    <button class="btn1">Button 1</button>
    <button class="btn2">Button 2</button>
    <button class="btn3">Button 3</button>
  </div>
  <script>
    // 이벤트 위임. 클릭된 하위 버튼 요소의 color를 변경한다.
    document.querySelector('.container').onclick = ({ target }) => {
      if (!target.matches('.container > button')) return;
      target.style.color = 'red';
    };

    // .btn2 요소는 이벤트를 전파하지 않으므로 상위 요소에서 이벤트를 캐치할 수 없다.
    document.querySelector('.btn2').onclick = e => {
      e.stopPropagation(); // 이벤트 전파 중단
      e.target.style.color = 'blue';
    };
  </script>
</body>
</html>

stopPropagation는 하위 DOM 요소의 이벤트를 개별적으로 처리하기 위해 이벤트의 전파를 중단시킨다.

 

9. 이벤트 핸들러 내부의 this

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

<button onclick="handleClick()">Click me</button>
<script>
function handleClick() {
  console.log(this); // window
}
</script>

이벤트 핸들러 어트리뷰트의 값으로 지정한 문자열은 암묵적으로 생성되는 이벤트 핸들러의 문이기 때문에 handleClick 함수는 이벤트 핸들러에 의해 일반함수로 호출된다. 일반함수로 호출되는 함수 내부의 this는 전역객체가 바인딩 된다.

 

단, 이벤트 핸들러로 호출할 때 인수로 전달한 this는 이벤트를 바인딩한 DOM 요소를 가리킨다.

<button onclick="handleClick(this)">Click me</button>
<script>
function handleClick(button) {
  console.log(button); // 이벤트를 바인딩한 button 요소
  console.log(this);   // window
}
</script>

 

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

이벤트 핸들러 프로퍼티 방식과 addEventListener 메서드 방식 모두 이벤트 핸들러 내부의 this는 이벤트를 바인딩한 DOM 요소를 가리킨다. 즉, 이벤트 핸들러 내부의 this는 이벤트 객체의 currentTarget 프로퍼티와 같다.

<!DOCTYPE html>
<html>
<body>
  <button class="btn1">0</button>
  <button class="btn2">0</button>
  <script>
    const $button1 = document.querySelector('.btn1');
    const $button2 = document.querySelector('.btn2');

    // 이벤트 핸들러 프로퍼티 방식
    $button1.onclick = function (e) {
      // this는 이벤트를 바인딩한 DOM 요소를 가리킨다.
      console.log(this); // $button1
      console.log(e.currentTarget); // $button1
      console.log(this === e.currentTarget); // true

      // $button1의 textContent를 1 증가시킨다.
      ++this.textContent;
    };

    // addEventListener 메서드 방식
    $button2.addEventListener('click', function (e) {
      // this는 이벤트를 바인딩한 DOM 요소를 가리킨다.
      console.log(this); // $button2
      console.log(e.currentTarget); // $button2
      console.log(this === e.currentTarget); // true

      // $button2의 textContent를 1 증가시킨다.
      ++this.textContent;
    });
  </script>
</body>
</html>

 

클래스에서 이벤트 핸들러를 바인딩 하는 경우 this에 주의해야 한다.

<!DOCTYPE html>
<html>
<body>
  <button class="btn">0</button>
  <script>
    class App {
      constructor() {
        this.$button = document.querySelector('.btn');
        this.count = 0;

        // increase 메서드를 이벤트 핸들러로 등록
        // this.$button.onclick = this.increase;

        // increase 메서드 내부의 this가 인스턴스를 가리키도록 한다.
        this.$button.onclick = this.increase.bind(this);
      }

      increase() {
        this.$button.textContent = ++this.count;
      }
    }

    new App();
  </script>
</body>
</html>

increase 메서드를 이벤트 핸들러로 바인딩할 때 bind메서드를 사용해 this를 전달하여 increase 메서드 내부의 this가 클래스가 생성할 인스턴스를 가리키도록 해야한다.

 

10. 이벤트 핸들러에 인수 전달

이벤트 핸들러 어트리뷰트 방식은 인수를 전달할 수 있지만, 이벤트 핸들러 프로퍼티 방식과 addEventListener 메서드 방식은 함수 자체를 등록하기 때문에 인수를 직접 전달할 수는 없다. 

그러나 아래와 같이 이벤트 핸들러 내부에서 함수를 호출하면서 인수를 전달할 수 있다.

<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>

 

또는 이벤트 핸들러를 반환하는 함수를 호출하면서 인수를 전달할 수도 있다.

// 이벤트 핸들러를 반환하는 함수
const checkUserNameLength = min => e => {
  $msg.textContent
    = $input.value.length < min ? `이름은 ${min}자 이상 입력해 주세요` : '';
};

// 이벤트 핸들러를 반환하는 함수를 호출하면서 인수를 전달한다.
$input.onblur = checkUserNameLength(MIN_USER_NAME_LENGTH);

 

11. 커스텀 이벤트

11.1 커스텀 이벤트 생성

Event, UIEvent, MouseEvent 같은 생성자 함수를 호출하여 명시적으로 생성한 이벤트 객체는 임의의 이벤트 타입을 지정할 수 있다. 이처럼 개발자의 의도로 생성된 이벤트를 커스텀 이벤트라고 한다.

// KeyboardEvent 생성자 함수로 keyup 이벤트 타입의 커스텀 이벤트 객체를 생성
const keyboardEvent = new KeyboardEvent('keyup');
console.log(keyboardEvent.type); // keyup

// CustomEvent 생성자 함수로 foo 이벤트 타입의 커스텀 이벤트 객체를 생성
const customEvent = new CustomEvent('foo');
console.log(customEvent.type); // foo

임의의 문자열을 사용하여 새로운 이벤트 타입을 지정하는 경우 CustomEvent 생성자 함수를 사용한다.

 

생성된 커스텀 이벤트 객체는 버블링되지 않으며 preventDefault 메서드로 취소할 수도 없다.

버블링과 preventDefault 메서드를 사용하려면 생성자 함수의 두번째 인수로 bubble, cancelable 프로퍼티를 갖는 객체를 전달한다.

const customEvent = new MouseEvent('click');
console.log(customEvent.type); // click
console.log(customEvent.bubbles); // false
console.log(customEvent.cancelable); // false

const customEvent2 = new MouseEvent('dbclick', {
  bubbles: true,
  cancelable: true
});

console.log(customEvent2.bubbles); // true
console.log(customEvent2.cancelable); // true

 

두번째 인수로 전달하는 객체에는 bubbles, cancelable 뿐만 아니라 이벤트 고유의 프로퍼티 값을 지정할 수 있다.

const mouseEvent = new MouseEvent('click', {
  bubbles: true,
  cancelable: true,
  clientX: 50,
  clientY: 100
});

console.log(mouseEvent.clientX); // 50
console.log(mouseEvent.clientY); // 100

 

11.2 커스텀 이벤트 디스패치

생성된 커스텀 이벤트는 dispatchEvent 메서드로 디스패치(이벤트를 발생시키는 행위)할 수 있다.

<body>
  <button class="btn">Click me</button>
  <script>
    const $button = document.querySelector('.btn');

    // 버튼 요소에 click 커스텀 이벤트 핸들러를 등록
    // 커스텀 이벤트를 디스패치하기 이전에 이벤트 핸들러를 등록해야 한다.
    $button.addEventListener('click', e => {
      console.log(e); // MouseEvent {isTrusted: false, screenX: 0, ...}
      alert(`${e} Clicked!`);
    });

    // 커스텀 이벤트 생성
    const customEvent = new MouseEvent('click');

    // 커스텀 이벤트 디스패치(동기 처리). click 이벤트가 발생한다.
    $button.dispatchEvent(customEvent);
  </script>
</body>

일반적으로 이벤트 핸들러는 비동기 처리방식으로 동작하지만 dispatchEvent 메서드는 동기 처리 방식으로 호출한다.

따라서 dispatchEvent 메서드로 이벤트를 디스패치하기 전에 커스텀 이벤트를 처리할 이벤트 핸들러를 등록해야 한다.

 

CustomEvent 생성자 함수로 임의의 이벤트 타입의 이벤트를 생성할 때 두 번째 인수로 이벤트와 함께 전달하고 싶은 정보를 담은 detail 프로퍼티를 포함하는 객체를 전달할 수 있다. 이 정보는 이벤트 객체의 detail 프로퍼티에 담겨 전달된다.

<body>
  <button class="btn">Click me</button>
  <script>
    const $button = document.querySelector('.btn');

    // 버튼 요소에 foo 커스텀 이벤트 핸들러를 등록
    // 커스텀 이벤트를 디스패치하기 이전에 이벤트 핸들러를 등록해야 한다.
    $button.addEventListener('foo', e => {
      // e.detail에는 CustomEvent 함수의 두 번째 인수로 전달한 정보가 담겨 있다.
      alert(e.detail.message);
    });

    // CustomEvent 생성자 함수로 foo 이벤트 타입의 커스텀 이벤트 객체를 생성
    const customEvent = new CustomEvent('foo', {
      detail: { message: 'Hello' } // 이벤트와 함께 전달하고 싶은 정보
    });

    // 커스텀 이벤트 디스패치
    $button.dispatchEvent(customEvent);
  </script>
</body>

기존 이벤트 타입이 아닌 임의의 이벤트 타입을 지정하여 커스텀 이벤트 객체를 생성한 경우 반드시 addEventListener 메서드 방식으로 이벤트 핸들러를 등록해야 한다.

 

 

 

Reference

- 이웅모 ,  모던 자바스크립트 Deep Dive , 위키북스 , 2020 

'개인공부 > 모던 자바스크립트 Deep Dive' 카테고리의 다른 글

25장 클래스(1)  (0) 2023.02.08
48장 모듈  (0) 2023.02.01
40장 이벤트(2)  (0) 2023.01.31
40장 이벤트(1)  (0) 2023.01.31
39장 DOM(4)  (0) 2023.01.29

댓글