개발 관련/javascript

13. 이벤트

lazysnack 2022. 7. 13. 17:48

다루는 내용

  1. 이벤트 흐름에 대한 이해
  2. 이벤트 핸들러 다루기
  3. 여러 가지 타입의 이벤트
  4. 메모리와 성능

1. 이벤트 흐름에 대한 이해

  • 자바스크립트와 HTML 의 상호작용은 문서나 브라우저 창에서 특정 순간에 일어난 일을 가리키는 이벤트에 의해 처리됨
  • 이벤트는 리스너(핸들러) 로 추적하며 리스너는 이벤트가 일어날 때만 실행됨

1.1 이벤트 버블링

  • 익스플로러의 이벤트 흐름

  • 문서트리에서 가장 깊이 위차한 요소에서 시작해 거슬러 올라가는 방식

    <html>
      <head>
        <title>title</title>
      </head>
      <body>
        <div id="myDiv">click me</div>
      </body>
    </html>
    • <div> 요소를 클릭하면

      1. <div>
      2. <body>
      3. <html>
      4. document

      의 순서대로 발생

1.2 이벤트 캡처링

  • 이벤트 버블링과는 반대로 최상위 노드에서 시작
  • 오래된 브라우저에서는 지원하지 않으므로 이벤트 버블링을 주로 사용함

1.3 DOM 이벤트 흐름

  • 이벤트 캡처링 단계, 타깃 단계, 이벤트 버블링 단계 3가지가 있음
  • 이벤트 캡처링, 이벤트 버블링 두 단계가 다 타깃 단계와 맞물리는 곳이 있으므로 결과적으로 타깃에서 이벤트를 작업할 기회가 두 번 생김 (??)

2. 이벤트 핸들러

  • 사용자 또는 브라우저가 취하는 특정 동작
  • on 으로 시작함
    • Ex) onclike, onload 등

2.1 HTML 이벤트 핸들러

  • 이벤트 핸들러 이름을 HTML 속성에 사용하여 할당할 수 있음

    <input type="button" value="click1" onclick="alert('clicked')"/>
    
    or
    
    <script type="text/javascript">
    function showMessage() {
      alert("hello!");
    }
    </script>
    <input type="buttion" value="click1" onclick="showMessage()"/>
  • 스코프 체인이 확장되어 함수 내부에서 document 와 해당 요소의 맴버에 마치 로컬 변수처럼 접근이 가능(with 를 통해 이루어짐)

    <form method="post">
        <input type="text" name="username" value="">
      <input type="button" value="echo" onclick="alert(username.value)">
    </form>
  • 이벤트 핸들러 코드가 준비되기 전에 HTML 요소가 먼저 화면에 발생하고 사용자가 이를 조작할 경우 에러가 발생할 가능성이 있음

  • 이벤트 핸들러 함수의 스코프 체인 확장 결과가 브라우저마다 다름

  • HTML 과 자바스크립트 간의 커플링이 심하게 될 수가 있음 (변경이 어려움)

2.2 DOM 레벨 0 이벤트 핸들러

  • 전통적으로는 이벤트 핸들러 프로퍼티를 함수에 할당하는 방법이 있음

    var btn = document.getElementById("myBtn");
    btn.onclick = function() {
        alert(this.id); // myBtn
    };
    
    // 이벤트 핸들러를 제거할 경우
    btn.onclick = null;

2.3 DOM 레벨 2 이벤트 핸들러

  • addEventListener(), removeEventListener()

  • 매개변수 (이벤트 이름, 이벤트 핸들러 함수, 이벤트 핸들러를 캡처 단계에서 호출여부)

    var btn = document.getElementById("myBtn");
    btn.addEventListener("click", function() {
      alert(this.id);
    }, false);
  • 위의 예제에서 함수는 익명함수 이기 때문에 removeEventListener 로 지울 수 없음. 따라서 해당 함수를 추가하고 지우고 하려면

    var btn = document.getElementById("myBtn");
    var handler = function() {
      alert(this.id);
    };
    btn.addEventListener("click", handler, false);
    
    //do something
    
    btn.removeEventListener("click", handler, false);

2.4 인터넷 익스플로러 이벤트 핸들러

  • IE8 및 이전 버전에서 사용
  • attachEvent(), detachEvent()

3. Event 객체 및 타입

  • DOM 과 관련된 이벤트가 발생하면 관련 정보는 모두 event 객체에 저장
  • 이벤트 핸들러 내부에서 this 객체는 항상 currentTarget 의 값과 일치하며 target에는 이벤트의 실제 타깃만 포함
  • preventDefault() 메소드는 이벤트의 기본 동작을 취소 (cancelable 이 true)
  • stopPropagation() 메소드는 이벤트 흐름을 멈춰서 이벤트 캡처링이나 버블링을 모두 취소
  • event 객체는 이벤트 핸들러가 아직 실행 중일 때만 존재하며 이벤트 핸들러가 실행을 마치면 event 객체는 파괴됨

3.1 이벤트 타입

  • UI 이벤트는 일반적인 브라우저 이벤트이며, BOM 과 상호작용이 포함될 수 있음
  • 포커스 이벤트는 요소가 포커스를 얻거나 잃을 때
  • 마우스 이벤트는 마우스로 어떤 동작을 취할 때
  • 등의 카테고리가 있으며, 이 외에도 HTML5 에서 정의한 이벤트 집합이 있음

3.2 UI 이벤트

  • 사용자와 직접 연관이 없으며, 하위 호환성을 위해 남겨진 것들
  • load - window 객체의 load 이벤트는 이미지나 자바스크립트 파일, CSS 파일 같은 외부 자원을 포함해 전체 페이지를 완전히 불러왔을 때 발생
  • unload - load 와는 반대로 문서를 완전히 닫을 때 발생하며 일반적으로 다른 페이지로 이동할 때 각종 참조를 제거하여 메모리 누수를 방지하는 목적으로 사용
  • resize - 브라우저 창의 높이나 너비를 바꿀 때 발생하며 브라우저별로 발생 시점에 차이가 있으므로 무거운 코드는 사용 지양

3.3 Focus 이벤트

  • 이벤트 요소가 포커스를 받거나 잃을 때 발생
  • 포커스를 잃는 요소에서 focusout, blur, DOMFocusOut 이 발생
  • 포커스를 얻는 요소에서 focusin, focus, DOMFocusIn 이 발생

3.4 마우스 이벤트와 휠 이벤트

  1. click - 사용자가 주요 마우스 버튼을 클릭하거나 엔터키를 누를 때 발생
  2. dbclick - 사용자가 주요 마우스 버튼을 더블클릭할 때 발생
  3. mousedown - 사용자가 마우스 버튼을 누를 때 발생 (키보드 발생 X)
  4. mouseenter - 마우스 커서가 요소 밖에서 요소 경계 안으로 처음 이동할 때
  5. mouseleaver - 마우스 커서가 요소 위에 있다가 요소 경계 밖으로 이동할 때
  6. mousemove - 마우스 커서가 요소 주변을 이동하는 동안 계속 발생
  7. mouseout - 마우스 커서가 요소 위에 있다가 다른 요소 위로 이동할 때 발생
  8. mouseover - 마우스 커서가 요소 바깥에 있다가 요소 경계 안으로 이동할 때 발생
  9. mouseup - 사용자가 마우스 버튼을 누르고 있다가 놓을 때 발생
  • 해당 이벤트가 지원되는지 확인하려면

    var isSupported = document.implementation.hasFeature("MouseEvents", "2.0");
    
    or 
    
    var isSupported = document.implementation.hasFeature("MouseEvent", "3.0");
  • 뷰포트 기준으로는 clientX/clientY, 페이지 기준으로는 pageX, pageY

  • mousewheel 이벤트는 사용자가 마우스 휠을 세로 방향으로 움직일 때 발생

  • 웹페이지 접근성을 위해(스크린 리더 등 호환)

    1. 코드 실행에는 click
    2. 사용자에게 새 옵션을 제시할 때 onmouseover 사용 금지
    3. 중요한 동작을 dbclick 로 실행 금지

3.5 키보드와 텍스트 이벤트

  1. keydown - 사용자가 키를 처음 누를 때 발생하며, 누르고 있는 동안에 계속 발생
  2. keypress - 사용자가 키를 누른 결과로 문자가 입력되었을 때 처음 발생, 누르고 있는 동안 계속 발생 (textInput 사용 권장)
  3. keyup - 사용자가 키에서 손을 뗄 때 발생

3.6 HTML5 이벤트

  1. contextmenu
    • 마우스 우클릭을 했을 때 나오는 메뉴같은 형태로, 컨텐스트 메뉴가 표시되려는 순간에 발생하므로 개발자가 기본 메뉴를 취소하고 커스터마이징 할 수 있음
    • 마우스 이벤트로 간주되므로 커서 위치와 관련된 프로퍼티를 포함
  2. beforeunload
    • window 에서 발생하며 개발자에게 페이지에서 떠나지 못하게 막을 방법을 제공할 의도로 만들어짐
  3. DOMContentLoaded
    • 항상 load 이벤트보다 먼저 발생하므로 이벤트 핸들러를 등록하거나 다른 DOM 조작을 수행하는 데 쓰임
    • window 의 load 이벤트가 페이지를 완전히 불러와야 발생하므로 외부 자원이 많을 경우 시간이 걸릴 수 있으므로 그럴 경우 DOMContentLoaded 를 등록
  4. readystatechange
    • 문서나 요소를 불러오는 상황에 대한 정보로 readyState 라는 프로퍼티
    • uninitialized, loading, loaded, interactive, complete 라는 순서가 있으나 모든 객체가 이 단계를 순서대로 전부 밟지는 않음

4. 메모리와 성능

  • 자바스크립트에서는 페이지에 존재하는 이벤트 핸들러의 개수가 페이지 성능에 직접적으로 영향을 미치는데
    1. 각 함수가 메모리를 점유하는 객체이기 때문
    2. 이벤트 핸들러를 많이 할당하려면 DOM 접근도 많아지며 이는 전체 페이지의 응답성을 떨어뜨리기 때문

4.1 이벤트 위임

  • 이벤트 핸들러의 갯수에 대해서는 이벤트 핸들러 하나만 할당해서 해당 타입의 이벤트를 모두 처리하는 방법이 있음

    <ul id="myList">
      <li id="go1">go1</li>
        <li id="go2">go2</li>
      <li id="hi">hi</li>
    </ul>
    
    <script>
    var list = document.getElementById("myList");
    var handler = function() {
      switch(this.id) {
        case "go1":
          document.title = "go1";
          break;
    
        case "go2":
          document.title = "go2";
          break;
    
        case "hi":
          document.title = "hi";
          break;
      }
    };
    list.addEventListener("click", handler, false);
    </script>
    • 이벤트가 버블링되어 올라오기 때문에 가능
    • 현실적이기만 하다면 이벤트 타입마다 document 에 이벤트 핸들러 단 하나씩만 등록해서 페이지의 이벤트 전체를 처리하는게 좋음

4.2 이벤트 핸들러 제거

  • 브라우저 코드와 자바스크립트 코드의 연결이 많을 수록 페이지가 느려지는데, 이 문제를 해결하기 위한 다른 방법(하나는 위의 위임 방법)은 더이상 필요하지 않은 이벤트 핸들러를 제거 하는 것

    <div id="myDiv">
      <input type="button" value="click me" id="myBtn">
    </div>
    
    <script>
        var btn = document.getElementById("myBtn");
      btn.onclick = function() {
        // doSomething
    
        btn.onclick = null; // 잔류 핸들러 제거
    
        document.getElementById("myDiv").innerHTML = "진행중";
      };
    </script>
  • 잔류 핸들러가 문제가 되는 다른 상황은 페이지를 떠날 때 이므로, 일반적으로 페이지를 떠나기 전에 onunload 이벤트 핸들러를 사용하여 이벤트 핸들러를 모두 제거하는 편이 좋음

  • onload 에서 한 일은 반드시 onunload에서 취소한다