자바스크립트 기술면접 9편

Genie·2021년 6월 2일
1
post-thumbnail
  • 이벤트 핸들러와 이벤트 핸들러 등록

이벤트가 발생했을 때 호출될 함수를 이벤트 핸들러라 하고,

이벤트가 발생했을 때, 브라우저에게 이벤트 핸들러의 호출을 위임하는 것을 이벤트 핸들러 등록이라 한다.


  • 이벤트 드리븐 프로그래밍

이벤트와 이벤트 핸들러를 통해 사용자와 애플리케이션은 상호작용할 수 있다.

이와 같이 프로그램의 흐름을 이벤트 중심으로 제어하는 프로그래밍 방식


  • 이벤트 타입

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

이벤트 타입은 200여가지가 있다.


  • DOMContentLoaded 이벤트와 load 이벤트의 차이점

DOMContentLoaded: HTML 문서의 로드와 파싱이 완료되어 DOM 생성이 완료되었을 때 발생

load: DOMContentLoaded 이벤트가 발생한 이후, 모든 리소스(이미지, 폰트)의 로딩이 완료되었을 때 발생


  • 이벤트 핸들러를 등록하는 방법
  1. 이벤트 핸들러 어트리뷰트 방식(함수 호출문을 할당, 인수를 전달하기 위해)
<button onclick="sayHi('Kim')">Click me</button>
<script>
    const sayHi = (name) => console.log(`Hi! ${name}`);
</script>
// 다음과 같은 함수를 암묵적으로 생성함, 그래서 함수 호출문으로 할당
const onclick = (event) => {
    sayHi('Kim');
};

⇒ 권장하지 않음(HTML과 JS는 관심사가 다르므로, 겸손한 자바스크립트)

⇒ CBD(Component Based Development) 방식의 React/Angular/Svelte/Vue.js 같은 프레임워크/라이브러리에서는 이벤트 핸들러 어트리뷰트 방식으로 이벤트를 처리한다. CBD에서는 HTML, CSS, JS를 관심사가 다른 개별적인 요소가 아닌, 뷰를 구성하기 위한 구성 요소로 보기 때문에 관심사가 다르다고 생각하지 않는다.

  1. 이벤트 핸들러 프로퍼티 방식
<button>Click me</button>
<script>
  const $button = document.querySelector('button');
  $button.onclick = () => console.log('버튼 클릭!');
</script>

⇒ 반드시 이벤트 타깃에 이벤트 핸들러를 바인딩해야 하는 것은 아니다. 이벤트 타깃 또는 전파된 이벤트를 캐치할 DOM 노드 객체에 바인딩한다. (이벤트 전파, 이벤트 위임)

장점: HTML, JS가 뒤섞이는 문제를 해결할 수 있다.

단점: 이벤트 핸들러 프로퍼티에 하나의 이벤트 핸들러만 바인딩할 수 있다.

  1. addEventListener 메서드 방식

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

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

      const handleClick = () => {
        console.log('버튼 클릭!');
      };
      
      $button.addEventListener('click', handleClick);
</script>

장점: 하나 이상의 이벤트 핸들러를 등록할 수 있다. 이때 이벤트 핸들러는 등록된 순서대로 호출된다.

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

      $button.addEventListener('click', function () {
        console.log('[1] 버튼 클릭!');
      });

      $button.addEventListener('click', function () {
        console.log('[2] 버튼 클릭!');
      });
</script>

⇒ focusin, focusout 이벤트는 addEventListener 메서드 방식으로 이벤트 핸들러를 등록해야한다.


  • 이벤트 핸들러 제거

addEventListener 메서드로 등록한 이벤트 핸들러를 제거하려면 removeEventListener 메서드를 사용한다.

<script>
      const $button = document.querySelector('button');

      // 이벤트 핸들러는 단 한번만 호출된다.
      $button.addEventListener('click', function foo() {
        console.log('버튼 클릭!');
        $button.removeEventListener('click', foo);
      });
</script>

  • 이벤트 객체

이벤트가 발생하면 이벤트에 관련한 다양한 정보를 담고 있는 이벤트 객체가 동적으로 생성된다.

생성된 이벤트 객체는 이벤트 핸들러의 첫 번째 인수로 전달된다.


  • target과 currentTarget의 차이점

target 프로퍼티: 이벤트를 발생시킨 DOM 요소

currentTarget 프로퍼티: 이벤트 핸들러가 바인딩된 DOM 요소

일반적으로 target 프로퍼티와 currentTarget 프로퍼티는 동일한 DOM 요소를 가리키지만, 이벤트 위임에서는 서로 다른 DOM 요소를 가리킬 수 있다.


  • transform: translate3d(x, y, z)

GPU 가속을 사용해 GPU 메모리에서 관련 작업을 진행한다. 따라서 매우 빠르다. 따라서 관련 작업의 레이아웃, 페인트 작업이 없어지게 된다. 반복적인 레이아웃, 페인트 단계가 없어지므로 성능이 좋다.


  • 이벤트 전파(event propagation)

DOM 트리 상에 존재하는 DOM 요소 노드에서 발생한 이벤트는 DOM 트리를 통해 전파된다.

생성된 이벤트 객체는 이벤트 타깃(event target)을 중심으로 DOM 트리를 통해 전파된다.


  • 이벤트 전파의 3단계(이벤트가 전파되는 방향에 따라 구분됨)
  1. 캡처링 단계: 이벤트가 상위요소에서 하위요소로 전파
  2. 타깃 단계: 이벤트가 이벤트 타깃에 도달
  3. 버블링 단계: 이벤트가 하위요소에서 상위요소 방향으로 전파

  • 이벤트 위임(event delegation)

여러 개의 하위 DOM 요소에 각각 이벤트 핸들러를 등록하는 대신 하나의 상위 DOM 요소에 이벤트 핸들러를 등록하는 방법


  • preventDefault

DOM 요소의 기본 동작을 중단시킨다.


  • stopPropagation

이벤트 전파를 중지시킨다.

하위 DOM 요소의 이벤트를 개별적으로 처리하기 위해 사용


  • 이벤트 핸들러 내부의 this
  1. 이벤트 핸들러 어트리뷰트 방식
<button onclick="handleClick()"></button>
<script>
      function handleClick() {
        console.log(this); // window
      }
</script>
<button onclick="handleClick(this)"></button>
<script>
      function handleClick(button) {
        console.log(button); // 이벤트를 바인딩한 button 요소
        console.log(this); // window
      }
</script>
  1. 이벤트 핸들러 프로퍼티 방식, addEventListener 메서드 방식

this ⇒ 이벤트를 바인딩한 DOM 요소, 즉 이벤트 핸들러 내부의 this는 currentTarget 프로퍼티와 같다.

<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) {
        console.log(this); // $button1
        console.log(e.currentTarget); // $button1

        ++this.textContent;
      };

      $button2.addEventListener('click', function (e) {
        console.log(this); // $button2
        console.log(e.currentTarget); // $button2

        ++this.textContent;
      });
</script>

단, 화살표 함수로 정의한 이벤트 핸들러 내부의 this는 상위 컨텍스트의 this를 가리킨다.

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

      $button1.onclick = (e) => {
        console.log(this); // window
        console.log(e.currentTarget); // $button1

        // this는 window를 가리키므로 window.textContent에 NaN(undefined + 1)을 할당한다.
        ++this.textContent;
      };

      $button2.addEventListener('click', (e) => {
        console.log(this); // window
        console.log(e.currentTarget); // $button2

        // this는 window를 가리키므로 window.textContent에 NaN(undefined + 1)을 할당한다.
        ++this.textContent;
      });
</script>

  • 이벤트 핸들러에 인수 전달
<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) => (e) => {
        $msg.textContent =
          $input.value.length < min ? `이름은 ${min}자 이상 입력해 주세요` : '';
      };

      $input.onblur = checkUserNameLength(MIN_USER_NAME_LENGTH);
</script>

  • 클로저(Closure)와 커링(Curring)
  1. 클로저 : 외부 함수의 변수(자유 변수)에 접근할 수 있는 내부 함수
const x = 1;

function outer() {
  const y = 2;

  function inner() {
    const z = 3;
    return x + y + z;
  }
  return inner;
}

const innerFunction = outer();
innerFunction(); // 6

⇒ inner()가 클로저 함수(클로저 함수에서는 지역변수(z), 외부 함수의 변수(y), 전역변수(x)에 모두 접근할 수 있다.)

  1. 커링 : 함수에 n개의 인자를 받는 대신, n개의 클로저 함수를 만들어 각각 인자를 받게하는 방법(클로저의 특징을 이용한 기법)

특히, 클로저의 외부 함수 변수(자유 변수)의 값을 고정할 수 있다는 점에서 유용한다.

function multiply(x, y, z) {
  return x * y * z;
}

multiply(3, 5, 7); // 3*5*7
multiply(3, 5, 8); // 3*5*8
multiply(3, 2, 1); // 3*2*1

multiply()의 첫 번째 인자는 모두 3, 3*5*73*5*8 은 두 번째 인자가 5

그러나 이 함수에서는 그때그때 모든 인자를 직접 지정해주어야 한다.

⇒ 이럴 때 커링을 이용해 특정 인자를 재사용할 수 있게 만들 수 있다.

function multiply(x) {
  return function (y) {
    return function (z) {
      return x * y * z;
    };
  };
}

const multiply3 = multiply(3); // 3이 고정됨
const multiply3And5 = multiply3(5); // 3과 5가 고정됨
const multiply3And2 = multiply3(2); // 3과 2가 고정됨

multiply3And5(7);
multiply3And5(8);
multiply3And2(1);

⇒ 클로저 함수의 외부 함수 변수(자유변수)의 값을 고정시킨 함수를 리턴한다.

커링을 사용하는 이유
1. 함수 확장이 쉽다.
2. 중복 인자를 피할 수 있다.
3. 특정 인자를 재사용할 수 있게 만들 수 있다.


  • 커스텀 이벤트

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

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

즉, dispatchEvent 메서드를 호출하면 커스텀 이벤트에 바인딩된 이벤트 핸들러를 직접 호출하는 것과 같다.

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

커스텀 이벤트 객체를 생성한 경우 반드시 addEventListener 메서드 방식으로 이벤트 핸들러를 등록해야 한다.


  • 타이머 함수 setTimeout과 setInterval

자바스크립트 엔진은 단 하나의 실행 컨텍스트 스택을 갖기 때문에 두 가지 이상의 테스크를 동시에 실행할 수 없다. 즉, 자바스크립트 엔진은 싱글 스레드로 동작한다.

이런 이유로 타이머 함수 setTimeout과 setInterval은 비동기 처리 방식으로 동작한다.


  • 디바운스와 스로틀

sroll, resize, input, mousemove 같은 이벤트는 짧은 시간 간격으로 연속해서 발생한다. 이러한 이벤트에 바인딩한 이벤트 핸들러는 과도하게 호출되어 성능에 문제를 일으킬 수 있다.

디바운스와 스로틀은 짧은 시간 간격으로 연속해서 발생하는 이벤트를 그룹화해서 과도한 이벤트 핸들러의 호출을 방지하는 프로그래밍 기법이다.


  • 디바운스

짧은 시간 간격으로 이벤트가 연속해서 발생하면 이벤트 핸들러가 호출되지 않다가 일정시간이 경과한 이후에 이벤트 핸들러가 한 번만 호출되도록 한다.

delay보다 짧은 간격으로 이벤트가 연속해서 발생하면 디바운스 함수의 첫 번째 인수로 전달한 콜백함수는 호출되지 않다가 delay 동안 input 이벤트가 더 이상 발생하지 않으면 한 번만 호출된다.

ex) resize 이벤트 처리, input 요소에 입력된 값으로 ajax 요청하는 입력필드 자동완성 UI 구현, 버튼 중복 클릭 방지


  • 스로틀

짧은 시간 간격으로 이벤트가 연속해서 발생하더라도 일정 시간 간격으로 이벤트 핸들러가 최대 한 번만 호출되도록 한다.

delay가 경과하기 이전에 이벤트가 발생하면 아무것도 하지 않다가 delay 시간이 경과했을 때 이벤트가 발생하면 콜백 함수를 호출하고 새로운 타이머를 재설정한다.

⇒ delay 시간 간격으로 콜백 함수가 호출된다.

ex) sroll 이벤트, 무한 스크롤 UI 구현


  • 자바스크립트와 블로킹(blocking)

자바스크립트 엔진은 단 하나의 실행 컨택스트 스택을 갖는다.

따라서 자바스크립트 엔진은 한 번에 하나의 테스크만 실행할 수 있는 싱글 스레드 방식으로 동작한다.

싱글 스레드 방식은 한 번에 하나의 테스크만 실행할 수 있기 때문에 처리에 시간이 걸리는 테스크를 실행할 경우 블로킹(blocking, 작업 중단)이 발생한다.


  • 동기(synchronous) 처리

: 현재 실행 중인 테스크가 종료할 때까지 다음에 실행 될 테스크가 대기하는 방식

장점: 테스크를 순서대로 하나씩 처리하므로 실행 순서가 보장된다.

단점: 테스크가 종료할 때까지 이후 테스크들이 블로킹된다.


  • 비동기(asynchronous) 처리

: 현재 실행 중인 테스크가 종료되지 않은 상태라 해도 다음 테스크를 곧바로 실행하는 방식

장점: 블로킹이 발생하지 않는다.

단점: 테스크의 실행 순서가 보장되지 않는다.

ex) 타이머 함수(setTimeout, setInterval), HTTP 요청, 이벤트 핸들러


  • 자바스크립트 엔진의 구성
  1. 콜 스택(call stack, 실행 컨텍스트 스택)
  2. 힙(heap): 객체가 저장되는 메모리 공간, 실행 컨텍스트는 힙에 저장된 객체를 참조한다.

⇒ 자바스크립트 엔진은 단순히 테스크가 요청되면 콜 스택을 통해 요청된 작업을 실행할 뿐이다.

⇒ 비동기 처리에서 소스코드의 평가와 실행을 제외한 모든 처리는 자바스크립트 엔진을 구동하는 환경인 브라우저 또는 Node.js가 담당한다.

ex)

자바스크립트 엔진 담당 : setTimeout의 콜백함수의 평가와 실행

브라우저 또는 Node.js 담당: 호출 스케줄링을 위한 타이머 설정과 콜백 함수 등록

⇒ 이를 위해 브라우저 환경은 테스크 큐와 이벤트 루프를 제공한다.


  • 이벤트 루프와 테스크 큐
    자바스크립트의 동시성을 지원하는 것이 이벤트 루프(브라우저에 내장된 기능 중 하나)이다.

ex) HTTP 요청을 통해 서버로부터 데이터를 가지고오면서 렌더링, HTML 요소가 애니메이션 효과를 통해 움직이면서 이벤트를 처리


  • 테스크 큐

타이머 함수와 같은 비동기 함수의 콜백 함수 또는 이벤트 핸들러가 일시적으로 보관되는 영역


  • 마이크로테스크 큐

프로미스의 후속처리 메서드의 콜백 함수가 일시적으로 보관되는 영역


  • 이벤트 루프(event loop)

자바스크립트의 동시성을 지원하는 것이 이벤트 루프(브라우저에 내장된 기능 중 하나)이다.

콜 스택에 현재 실행 중인 실행 컨텍스트가 있는지, 그리고 테스크 큐에 대기 중인 함수가 있는지 반복해서 확인한다.

만약 콜스택이 비어있고, 태스크 큐에 대기 중인 함수가 있다면 이벤트 루프는 순차적으로(FIFO) 태스크 큐에 대기 중인 함수를 콜 스택으로 이동시킨다.


  • 자바스크립트 엔진과 브라우저

자바스크립트 엔진은 싱글 스레드로 동작하지만, 브라우저는 멀티 스레드로 동작한다.

만약 자바스크립트 코드가 자바스크립트 엔진에서 싱글 스레드 방식으로 동작한다면 비동기로 동작할 수 없다.

따라서 브라우저와 자바스크립트 엔진이 협력하여 비동기 함수를 실행한다.


  • Ajax(Asynchronous Javascript And XML)

자바스크립트를 사용하여 브라우저가 서버에게 비동기 방식으로 데이터를 요청하고, 서버가 응답한 데이터를 수신하여 웹 페이지를 동적으로 갱신하는 프로그래밍 방식

Ajax는 브라우저에서 제공하는 Web API인 XMLHttpRequest 객체를 기반으로 동작한다.

XMLHttpRequest는 HTTP 비동기 통신을 위한 메서드와 프로퍼티를 제공한다.

XMLHttpRequest는 큰 주목을 받지 못하다가 구글 맵스를 통해 JS의 가능성을 확인하는 계기를 마련했다.

웹 브라우저에서 JS와 Ajax를 기반으로 동작하는 구글 맵스가 손색이 없을 정도의 퍼포먼스와 부드러운 화면 전환 효과를 보여주었다.


  • 전통적인 웹 페이지 방식의 단점
  1. 변경이 필요없는 부분도 다시 요청하기 때문에 불필요한 데이터 통신(트래픽)이 발생한다.
  2. 변경이 필요없는 부분도 처음부터 다시 렌더링 하기 때문에 순간적으로 화면이 깜빡이는 현상이 발생한다.
  3. 클라이언트와 서버와의 통신이 동기 방식으로 동작하기 때문에 서버로부터 응답이 있을 때까지 다음 처리는 블로킹된다.

  • Ajax의 장점
  1. 필요한 데이터만 서버로 부터 전송받기 때문에 불필요한 데이터 통신(트래픽)이 발생하지 않는다.
  2. 변경할 필요가 없는 부분은 다시 렌더링하지 않기 때문에 화면이 순간적으로 깜빡이는 현상이 발생하지 않는다.
  3. 클라이언트와 서버와의 통신이 비동기 방식으로 동작하기 때문에 서버에게 요청을 보낸 이후 블로킹이 발생하지 않는다.

  • JSON(JavaScript Object Notation)

클라이언트와 서버 간의 HTTP 통신을 위한 텍스트 데이터 포멧


  • XMLHttpRequest

브라우저 주소창이나 HTML의 form 태그, a태그를 통해 HTTP 요청 전송 기능을 기본 제공한다.

자바스크립트를 사용하여 HTTP 요청을 전송하려면 XMLHttpRequest 객체를 사용한다.

Web API인 XMLHttpRequest 객체는 HTTP 요청 전송과 HTTP 응답 수신을 위한 다양한 메서드와 프로퍼티를 제공한다.


  • HTTP 요청 메서드

  • MIME 타입

클라이언트에게 전송된 문서의 다양성을 알려주기 위한 매커니즘

브라우저들은 리소스를 내려받았을 때 해야 할 기본 동작이 무엇인지를 결정하기 위해 MIME 타입을 사용한다.

// HTTP 요청 헤더 설정
// 클라이언트가 서버로 전송할 데이터의 MIME 타입 지정: json
xhr.setRequestHeader('content-type', 'application/json');
// 서버가 응답할 데이터의 MIME 타입 지정: json
xhr.setRequestHeader('accept', 'application/json');

⇒ 만약 Accept 헤더를 설정하지 않으면 send 메서드가 호출될 때 Accept 헤더가 */* 으로 전송된다.

profile
front-end engineer

0개의 댓글