모던 자바스크립트 입문] 9주차 15.1~15.5

qqEQ·2021년 8월 3일
0

이미지

모던 자바스크립트 입문

15.1 이벤트 처리기를 등록하는 방법

15.1.1

이벤트가 발생했을 때 실행되는 함수를 이벤트처리기 또는 이벤트 리스너라고 합니다.
등록방법

  1. HTML요소의 이벤트 처리기 속성에 설정하는 방법
  2. <input type='button' onclick='changeColor();'>
  3. DOM 요소 객체의 이벤트 처리기 프로퍼티에 설정하는 방법
  4. var btn = document.getElementById('button'); btn.onclick = changeColor();
  5. addEventListener 메서드를 사용하는 방법
  6. var btn = document.getElementById('button'); btn.addEventListener('click', changeColor, false);

15.1.2 이벤트 처리기의 문제점

  1. HTML요소의 이벤트 처리기 속성에 설정할 경우
  • HTML문서를 읽어들일 때 이벤트 처리기도 함께 설정하기 때문에 설정하기 쉬움.
  • HTML과 JS가 뒤섞여 가독성이 떨어짐 -> 유지보수성이 떨어짐
  • 특정 요소의 특정 이벤트에 대해 이벤트 처리기를 단 하나만 등록할 수 있음
    그 요소에 똑같은 종류의 이벤트처리기를 중복해서 등록할 경우 함수를 덮어쓰게됨.
  1. DOM 요소 객체의 이벤트 처리기 프로퍼티에 설정할 경우
  • HTML과 JS를 분리해서 프로그램 작성가능 -> 유지보수성 증가
  • 특정 요소의 특정 이벤트에 대해 단 하나의 이벤트 처리기만 등록가능.

15.2 이벤트 리스너를 등록하고 삭제하는 방법

15.2.1 addEventListener 메서드로 이벤트 리스너 등록하기

addEventListener를 이용하면 같은 이벤트 종류에 대해 여러개의 이벤트 리스너를 등록할 수 있음.

target.addEventListener(type, listener, useCapture);

target : 이벤트 리스너를 등록할 DOM노드
type : 이벤트 유형을 뜻하는 문자열('click', 'mouseup' 등)
listener : 이벤트가 발생했을 때 처리를 담당하는 콜백함수의 참조
useCapture : 이벤트 단계 ( true: 캡처링단계, false : 버블링단계(default)

<script>
  window.onload = function() {
    var element = document.getElementById('box');
    element.addEventListener('click', function(e) {
      e.currentTarget.style.backgroundColor = 'red';
    }, false);
  }
</script>
<!--... -->
<body>
    <div id='box'>click me</div>
</body>

이벤트 리스너 함수가 받는 인수 e는 이벤트객체이며, 이벤트객체 안에는 발생한 이벤트의 정보가 담겨있습니다.
e.currentTarget은 클릭한 요소를 참조하는 요소객체입니다.
type에는 on이 빠진 이벤트타입이 대입됩니다.

listener부분에는 익명함수외에도 선언한 함수를 넣어줄 수 있습니다.

<script>
  window.onload = function() {
    var element = document.getElementById('box');
    element.addEventListener('click', changeColor, false);
  }
  function changeColor(e) {
    e.currentTarget.style.backgroundColor = 'red';
  }
</script>
<body>
  <div id='box'>click me</div>
</body>

changeColor함수를 선언한 후 해당 함수를 addEventListener를 통해 대입해줌.

15.2.2 addEventListener를 통해 얻을 수 있는 장점

  • 같은 요소의 같은 이벤트에 이벤트 리스너 여러개를 등록할 수 있음.
  • 버블링 단계는 물론 캡처링 단계에서도 활용할 수 있음.
    반면 DOM요소 객체에 직접 등록한 이벤트 처리기는 버블링 단계의 이벤트만 캡처가능.
  • removeEventListener, stopPropagation, preventDefault를 활용하여 이벤트 전파를 정밀제어할 수 있음
  • HTML요소를 포함한 모든 DOM 노드에 이벤트 리스너를 등록할 수 있음.

15.2.3 removeEventListener 메서드로 이벤트 리스너 삭제하기

target.removeEventListener(type, listener, useCapture);

주의
만약 addEventListener에 listener를 익명함수로 등록했다면
해당 이벤트리스너는 removeEventListener로 삭제할 수 없습니다
삭제하려면 그 함수의 참조를 변수나 배열에 저장하고 있어야합니다.

이벤트 리스너 안에서 removeEventListener를 호출하여 이벤트 리스너가 스스로 삭제하게 하기 위해선
listener에 arguments.callee를 넘깁니다.
(익명함수 내에서 removeEventListener를 호출하는 callee는 익명함수 그 자체이기 때문)

window.onload = function() {
  var element = document.getElementById('box');
      element.addEventListener('click', function() {
    console.log('hello');
    // removeEventListener 함수가 호출되는 순간에서의 호출자(callee)는 익명함수이므로
    element.removeEventListener('click', arguments.callee, false);
  }, false);
}

15.3 이벤트객체 (e)

15.3.1 이벤트 객체

이벤트처리기와 이벤트리스너는 이벤트객체를 인수로 받습니다. (function(e) {...})
이벤트객체는 해당 이벤트의 다양한 정보를 저장한 프로퍼티와 이벤트의 흐름을 제어하는 메서드를 가지고 있습니다.

// 이벤트 객체 e
e.currentTarget.style.backgroundColor = 'red';

이벤트객체가 가지는 프로퍼티는 발생한 이벤트의 유형에 따라 달라집니다.

15.3.2 이벤트 객체의 공통 프로퍼티

프로퍼티값의 타입설명
type문자열이벤트 이름('click', 'mousedown', 'keydown' 등)
target요소 객체이벤트가 발생한 요소
currentTarget요소 객체처리를 담당하는 이벤트 리스너가 등록된 요소 객체
eventPhase정수이벤트 전파 단계(1: 캡처링, 2: 타겟, 3: 버블링 단계)
timeStamp정수이벤트 발생시각(1970/01/01/00:00:00부터 경과된 밀리초)
bubbles논리값버블링 단계인지를 뜻하는 값
cancelable논리값preventDefault()로 기본이벤트를 취소할 수 있는지를 뜻하는 값
defaultPrevented논리값preventDefault()로 기본작업이 취소되었는지를 듯하는 값
isTrusted논리값해당 이벤트가 사용자의 액션에 의해 생성되었는지를 뜻하는 값

15.3.3 마우스 이벤트 객체

click, dblclick, mousedown, mouseup, mousemove, mouseout, mouseover 등이 있습니다.

프로퍼티값의 타입설명
screenX, screenY정수클릭한 위치의 화면 좌표(컴퓨터 화면 왼쪽 위 기준)
clientX, clienY정수클릭한 위치의 윈도우 좌표(표시영역의 왼쪽 위 기준, 브라우저의 각 탭)
pageX, pageY정수클릭한 위치의 문서 좌표(문서 왼쪽 위 기준)
offsetX, offsetY정수이벤트가 발생한 요소의 상대좌표(요소의 왼쪽 위 꼭지점 기준)
altKey논리값alt버튼이 눌렸는지 뜻하는 논리값
ctrlKey논리값ctrl이 눌렸는지 뜻하는 논리값
shiftKey논리값shift가 눌렸는지 뜻하는 논리값
detail정수이벤트의 자세한 정보 : 마우스 이벤트의 경우 클릭한 횟수
button정수눈린 마우스의 버튼(왼쪽 : 0, 휠 : 1, 오른쪽 : 2)
relatedTarget객체mouseover 이베트에서는 마우스가 떠난 노드
  mouseout 이벤트에서는 마우스가 들어온 노드

[##Image|kage@crWfXq/btq54OAmyEa/vD5ronWUyR7yjr1meCu6Lk/img.jpg|alignCenter|width="100%" data-origin-width="1439" data-origin-height="1251" data-ke-mobilestyle="widthOrigin"|||##]

window.onload = function() {
            // 보더 박스를 생성한다.
            var box = elt('div', null, 'JavaScript');
            document.body.appendChild(box);
            // 마우스를 press한 상대 위치를 저장하는 변수
            var boxOffsetX, boxOffsetY;

            // 보더박스의 설정
            box.style.display = 'inline-block';
            box.style.position = 'absolute';
            box.style.padding = '10px';
            box.style.backgroundColor = 'blue';
            box.style.color = 'white';
            box.style.cursor = 'pointer';

            // mousedown 이벤트 리스너 등록
            box.addEventListener('mousedown', function mouseDownListener(e) {
                // mousemove 이벤트 리스너를 등록한다.
                document.addEventListener('mousemove', mouseMoveListener, false);

                // 마우스를 press한 상대 위치를 저장
                boxOffsetX = e.offsetX;
                boxOffsetY = e.offsetY;
            }, false);

            // mouseup 이벤트 리스너 등록
            document.addEventListener('mouseup', function mouseUpListener(e) {
                // mousemove 이벤트 리스너를 제거한다.
                document.removeEventListener('mousemove', mouseMoveListener, false);

            }, false);

            // mousemove 이벤트 리스너
            function mouseMoveListener(e) {
                // 마우스로 press한 위치를 드래그한다.
                box.style.left = e.pageX - boxOffsetX + 'px';
                box.style.top = e.pageY - boxOffsetY + 'px';
            }
        }

클릭(mousedown) 시 mousemove이벤트가 등록되어 움직이면 박스위치가 변경됩니다.
mouseup시 박스가 움직이지 않도록 mousemove이벤트를 제거해줍니다.

  • 이미지(img)요소 드래그를 통해 옮길 경우의 주의점
    src속성을 지정한 img 요소는 기본적으로 드래그할 수 있도록 되어있습니다
    이런 경우 사용자가 mouseup, mousemove를 이벤트리스너에 등록해도
    브라우저에서 그 이벤트를 취소해버리므로 이벤드가 정상동작하지 않습니다.
    이를 방지하기 위해 mousedown이벤트 리스너 안에서 이벤트 객체의 preventDefault메서드를 실행해야합니다.

15.3.4 키보드 이벤트 객체

프로퍼티값의 타입설명
altKey논리값alt가 눌렸는지 뜻하는 논리값
ctrlKey논리값ctrl이 눌렸는지 뜻하는 논리값
shiftKey논리값shift가 눌렸는지 뜻하는 논리값
metaKey논리값Meta키가 눌렸는지 뜻하는 논리값(맥 : command, 윈도우 : 윈도우버튼)
key문자열눌린 키의 DOMString
keyCode정수눌린 키의 코드
code문자열눌린 키가 키보드에서 차지하는 물리적 위치를 뜻하는 문자열

keyCode의 경우 알파벳, 숫자일때만 올바르게 표시됩니다.
또한 keyCode값은 shift, ctrl을 눌러도 바뀌지 않습니다.

15.4 이벤트 전파

Window객체나 XMLHttpRequest객체처럼 단독으로 존재하는 객채에서 이벤트가 발생하면
웹브라우저는 해당 객체에 등록된 이벤트 처리기를 호출합니다. 그러나 이벤트가 HTML요소에서 발생하면 그 요소는 물론 그 요소의 모든 조상요소에 이벤트를 전파합니다. 결과적으로 그 모든 요소에 등록된 이벤트 처리기가 호출됩니다.

15.4.1 이벤트의 단계

HTML 요소에서 클릭 같은 사용자 행위와 프로그램 코드가 이벤트를 발생시킵니다.
이벤트가 발생한 요소는 이벤트 타겟이라고 합니다.
또한 이벤트를 발생시키는 것을 가리켜 이벤트를 발사(fire)한다고 표현합니다.
요소에서 이벤트가 발생하면 DOM트리의 관련요소(조상요소) 전체에
그 이벤트에 반응하는 이벤트 처리기나 이벤트 리스너가 등록되어있는지 확인하는 작업을 합니다.
그리고 등록된 함수가 있을때는 그 함수를 실행하도록 되어있습니다.

  1. 캡처링단계
    이벤트가 Window객체에서 출발하여 DOM트리를 타고 이벤트 타겟까지 전달됩니다.
    이 단계에서 반응하도록 등록된 이벤트리스너
    이벤트가 발생한 요소에 등록된 이벤트처리기나 이벤트리스너보다 먼저 실행됩니다.
    이 단계는 이벤트 타겟이 이벤트를 수신하기 전에 이벤트를 빼돌리는(캡처하는) 단계라는 뜻입니다.
  2. 타겟 단계
    이벤트가 이벤트 타겟에게 전달되는 단계입니다.
    이벤트 타겟에 등록된 이벤트 처리기나 이벤트 리스너는 이 시점에 실행됩니다.
  3. 버블링 단계
    이벤트가 이벤트 타겟에서 출발해서 DOM트리를 타고 Window객체까지 전파됩니다.
    이 단계에서 반응하도록 등록된 이벤트 리스너는
    이벤트가 발생한 요소에 등록된 이벤트 처리기나 이벤트 리스너 다음에 실행됩니다.
    마치 거품(버블)이 올라오는것처럼 DOM트리 아래에서부터 위로 올라온다는 의미입니다.
    단, focus와 blur이벤트는 그 요소에만 필요한 이벤트이므로 버블링이 발생하지 않습니다.
    이벤트 처리기를 등록했다면 이벤트 처리기는 타겟단계와 버블링단계 모두 실행됩니다.
    이벤트 리스너는 실행할 단계를 선택할 수 있습니다. 이벤트 리스너의 useCapture가 true면
    타겟단계와 캡처링 단계일때 실행됩니다.
    useCapture가 false이면 타겟단계와 버블링단계일때만 실행됩니다.

이벤트 전파

window.onload = () => {
  var outer = document.getElementById('outer');
  var inner2 = document.getElementById('inner2');
  outer.addEventListener('click', function(e) {
    console.log('outer bubbling');
  }, false);
  outer.addEventListener('click', function(e) {
    console.log('outer capturing');
  },true);
  inner2.addEventListener('click', function(e) {
    console.log('inner2 bubbling');
  },false);
}

inner2 클릭 시 아래와 같이 콘솔이 나타납니다.

15.4.2 이벤트의 전파

자식요소에서 발생한 이벤트는 부모 요소에서도 전파됩니다.
때문에 의도치 않은 동작을 할 경우가 있는데, 이벤트 전파를 취소해서 부모요소가 이벤트처리를 하지 않도록 만들 수 있습니다.
또한 이벤트가 발생했을때 웹브라우저의 기본 동작도 취소할 수 있습니다.

  • 이벤트 전파 취소하기 : stopPropagation
event.stopPropagation();

이벤트 리스너 안에서 이벤트 객체의 stopPropagation메서드를 호출하면 이벤트가 그 다음 요소로 전달되는것을 막습니다.

window.onload = () => {
  var outer = document.getElementById('outer');
  var inner2 = document.getElementById('inner2');
  outer.addEventListener('click', function(e) {
    console.log('outer bubbling');
  }, false);
  outer.addEventListener('click', function(e) {
    console.log('outer capturing');
  }, true);
  inner2.addEventListener('click', function(e) {
    console.log('inner2 (1)');
    e.stopPropagation(); // 이벤트 전파를 취소한다.
  }, false);
  inner2.addEventListener('click', function(e) {
    console.log('inner2 (2)');
  }, false);
}

stopPropagation메서드를 통해 버블링 단계에서 이벤트를 전파하지 않습니다.

  • 이벤트 전파의 일시 정지 : stopImmediatePropagation
event.stopImmediatePropagation();

해당 객체에 등록된 다른 이벤트 리스너도 일시적으로 멈춥니다.

window.onload = () => {
  var outer = document.getElementById('outer');
  var inner2 = document.getElementById('inner2');
  outer.addEventListener('click', function(e) {
    console.log('outer bubbling');
  }, false);
  outer.addEventListener('click', function(e) {
    console.log('outer capturing');
  }, true);
  inner2.addEventListener('click', function(e) {
    console.log('inner2 (1)');
    e.stopImmediatePropagation(); // 이벤트 전파를 일시정지한다
  }, false);
  inner2.addEventListener('click', function(e) {
    console.log('inner2 (2)');
  }, false);
}

  • 기본동작 취소하기 : preventDefault
event.preventDefault();

웹브라우저에 구현된 기본동작을 취소할 수 있습니다.

window.onload = () => {
  var anchor = document.getElementById('school');
  anchor.addEventListener('click', function(e) {
    if (!confirm('페이지를 이동하시겠습니까?')) {
      e.preventDefault();
    }
  }, false);
}

확인 버튼을 누르면 이동, 취소를 누르면 브라우저에 구현된 a태그의 기본동작이 되지 않습니다.
다만 이벤트 객체의 cancelable이 true일경우만 preventDefault로 취소가능합니다.

15.5 이벤트 리스너 안의 this

이벤트 리스너 안의 this값은 함수를 호출할 때 그 함수가 속해있던 객체의 참조입니다.

15.5.1 이벤트 리스너 안의 this는 이벤트가 발생한 요소 객체

window.onload = () => {
  function Person(name) {
    this.name = name;
  };

  Person.prototype.sayHello = function() {
    console.log(this);
    console.log('Hello' + this.name);
  }

  var person = new Person('Tom');
  var button = document.querySelector('#button');
  button.addEventListener('click', person.sayHello, false);

}

버튼을 클릭하면 person.sayHello가 실행되고, 그 안의 this가 person을 가리킬거라 기대하지만 실제로는 button요소객체를 가리킵니다.
button객체의 name프로퍼티값은 빈문자열이므로 의도와는 다르게 표시됩니다.

15.5.2 this가 원하는 객체를 가리키도록 하는 방법

  • bind를 이용하는 방법
    함수 객체의 bind메서드를 사용하면 그 함수가 실행될때의 this를 지정할 수 있습니다.
window.onload = function() {
  function Person(name) {
    this.name = name;
  }
  Person.prototype.sayHello = function() {
    console.log('hello' + this.name);
  };
  
  var person = new Person('Tom');
  var button = document.querySelector('#button');
  button.addEventListener(
    'click', 
    person.sayHello.bind(person), 
    false
  );
};
  • 익명함수 안에서 실행하는 방법

이벤트 처리기 또는 이벤트 리스너로 익명 함수를 넘기고, 익명 함수 안에서 메서드를 호출하면 그 메서드의 this가 메서드를 참조하는 객체를 가리킵니다.

button.addEventListener('click', function(e) {
  person.sayHello();
}, false);
  • 화살표 함수를 사용하는 방법

화살표 함수에서의 this는 함수를 초기화하는 시점에 결정됩니다.
이를 활용하여 객체 안의 메서드를 화살표 함수로 표기하면 그 안의 this가 생성된 객체를 가리킵니다.

window.onload = () => {
  function Person(name) {
    this.name = name;
    this.sayHello = () => {
      console.log('hello ' + this.name);
    }
  };
  const person = new Person('Tom');
  const button = document.querySelector('#button');
  button.addEventListener('click', person.sayHello, false);
}

단, 화살표 함수 표현식으로 정의한 함수를 객체의 prototype의 메서드로 추가하면 의도한대로 동작하지 않습니다.

// prototype에 화살표 함수로 this를 사용하는 경우
// Window객체를 가리키게됨
Person.prototype.sayHello = () => {
  console.log('hello ' + this.name);
}
  • addEventListener의 두번째 인수로 객체를 넘기는 방법

addEventListener의 두번째 인수는 함수이지만 대신 객체를 넘길수도 있습니다. 그리고 그 객체에 handleEvent 메서드가 있으면 그 메서드를 이벤트 리스너로 등록하도록 만들어져있습니다.

function Person(name) {
  this.name = name;
}

Person.prototype.handleEvent = function() {
  console.log('hello ' + this.name);
}

var person = new Person('Tom');
var button = document.getElementById('button');
button.addEventListener('click', person, false);
profile
큐큐익규

0개의 댓글

관련 채용 정보