번역)2024년 모든 자바스크립트 개발자가 알아야할 인터뷰 질문

7
post-thumbnail

원문: https://medium.com/javascript-scene/10-interview-questions-every-javascript-developer-should-know-in-2024-c1044bcb0dfb

JavaScript의 세계는 크게 발전했으며 인터뷰의 트렌드 역시 수년에 걸쳐 많은 변화를 겪었습니다. 해당 가이드는 2024년에 모든 JavaScript 개발자가 알아야 하는 10가지 필수 질문들을 제공합니다. 본 가이드는 closure에서부터 TDD까지 다양한 주제를 다루며, 최신 JavaScript 문제에 대처할 수 있는 지식과 자신감을 제공합니다.

1. Closure란 무엇인가?

closure는 내부 함수에서 외부 함수의 범위에 접근할 수 있게 해줍니다.

함수가 중첩되면 내부 함수는 외부 함수 범위에서 선언된 변수에 접근할 수 있으며, 심지어 외부 함수가 반환된 후에도 접근이 가능합니다.

const createSecret = (secret) => {
  return {
    getSecret: () => secret,
    setSecret: (newSecret) => {
      secret = newSecret;
    },
  };
};

const mySecret = createSecret("My secret");
console.log(mySecret.getSecret()); // My secret

mySecret.setSecret("My new secret");
console.log(mySecret.getSecret()); // My new secret

Closure 변수는 복사본이 아닌 외부 범위 변수에 대한 실제 참조입니다.

이는 외부 범위 변수를 변경하면 해당 변경이 closure 변수에 반영되며 그 반대도 마찬가지라는 것을 의미합니다. 즉, 동일한 외부 함수에서 선언된 다른 함수들도 변경 사항에 접근할 수 있음을 의미합니다.

클로저의 일반적인 사용 사례는 다음과 같습니다.

  • 데이터 보호
  • Currying 및 Partial applications
    • partial application은 기존 함수의 매개변수들 중 일부를 미리 넣어둔 새로운 함수를 만드는 것이고, 만들어진 partial application 함수는 다음 번 호출 시에는 결과를 반환해야 한다.
    • currying은 기존 함수의 매개변수를 하나씩 받는 방법이고, 매개변수를 모두 받을 때까지 함수를 반환한다.
  • 이벤트 핸들러와 콜백 함수를 통한 데이터 공유

데이터 보호

캡슐화는 객체 지향 프로그래밍의 핵심 기능입니다.

이를 통해 외부부터 클래스의 구현 세부 사항을 숨길 수 있습니다. JavaScript의 클로저를 사용하면 객체에 대한 비공개 변수를 선언할 수 있습니다.

const createCounter = () => {
  let count = 0;
  return {
    increment: () => ++count,
    decrement: () => --count,
    getCount: () => count,
  };
};

Currying 및 Partial applications

// Currying 함수는 한 번에 하나의 인수를 취합니다.
const add = (a) => (b) => a + b;

// Partial application은 함수의 일부 인수가 적용되었지만, 아직 모든 인수가 적용되지 않은 함수입니다.
const increment = add(1); // Partial application

increment(2); // 3

2. 순수 함수란 무엇인가?

순수 함수는 함수형 프로그래밍에서 중요한 개념입니다.

순수 함수는 예측 가능하므로 비순수 함수보다 이해, 디버깅, 테스트하기 쉽습니다.

순수 함수는 아래의 두 규칙을 따릅니다.

  1. Deterministic(결정론적): 동일한 입력이 주어지면 항상 동일한 출력 값을 반환합니다.

    • 비결정론적 함수의 예시
      • 난수 생성, Math.random()
      • 현재 시간, Date.now()
      • 상태를 변경할 수 있는 전역 변수
      • 상태를 변경할 수 있는 parameter
  2. No side-effect(사이드 이펙트가 없다): 함수 외부의 상태를 변경하지 않습니다.

    • 사이드 이펙트의 예시
      • 외부 변수 또는 객체 속성 변경
      • 콘솔에 로그 출력
      • 화면 또는 파일, 네트워크에 쓰기
      • 오류 또는 예외 발생, 대신 함수는 반드시 오류를 나타내는 결과를 반환해야 합니다.
      • 외부 프로세스 트리거

3. 함수 합성이란 무엇인가?

함수 합성은 두 개 이상의 함수를 조합하여 새로운 함수를 생성하거나 어떤 계산을 수행하는 과정입니다.
예를 들어 (f ∘ g)(x) = f(g(x))fg를 합성한 함수이며 xfg의 인수입니다.

const compose = (f, g) => (x) => f(g(x));

const g = (num) => num + 1;
const f = (num) => num * 2;

const h = compose(f, g);

h(20); // 42

React 개발자는 함수 합성을 통해 대규모 컴포넌트 트리를 정리할 수 있습니다. 컴포넌트를 중첩하는 대신, 컴포넌트들을 새로운 고차 컴포넌트로 만들 수 있습니다. 이 고차 함수를 통해 기존 컴포넌트에 새로운 기능을 추가할 수 있습니다.

4. 함수형 프로그래밍이란 무엇인가?

함수형 프로그래밍은 순수 함수를 주요 구성 단위로 사용하는 프로그래밍 패러다임입니다.

소프트웨어 개발에서 구성은 매우 중요하므로 사실상 모든 프로그래밍 패러다임은 구성 단위의 이름을 따서 명명됩니다.

  • Object-oriented programming(객체 지향 프로그래밍)은 객체를 구성 단위로 사용합니다.
  • Procedural programming(절차적 프로그래밍)은 구성 단위로 절차를 사용합니다.
  • Functional programming(함수형 프로그래밍)은 함수를 구성 단위로 사용합니다.

함수형 프로그래밍은 선언적 프로그래밍 패러다임입니다. 즉, 프로그램은 프로그램을 수행하는 방법이 아닌 수행하는 작업에 따라 작성됩니다. 이는 기능적 프로그램을 명령형 프로그램보다 이해하고 디버깅하고 테스트하기 쉽게 만듭니다. 또한 훨씬 더 간결해지는 경향이 있어 코드 복잡성이 줄어들고 유지 관리가 더 쉬워집니다.

함수형 프로그래밍의 다른 주요 특징은 다음과 같습니다.

  • Immutability(불변성)
    • 불변 데이터 구조는 가변 데이터 구조보다 추론하기가 더 쉽습니다.
  • Higher-order functions(고차 함수)
    • 다른 함수를 인수로 사용하거나 함수를 결과로 반환하는 함수입니다.
  • Avoiding shared mutable state(공유된 가변 상태 피하기)
    • 공유된 가변 상태는 프로그램을 이해하고 디버그하고 테스트하기 어렵게 만듭니다. 이는 또한 프로그램의 정확성에 대해 추론하기 어렵게 만듭니다.

순수 함수는 테스트하기 쉽기 때문에 함수형 프로그래밍은 더 나은 테스트 적용 범위와 더 적은 버그로 이어지는 경향이 있습니다.

5. Promise란 무엇인가?

JavaScript의 Promise는 비동기 작업의 최종 완료 또는 실패를 나타내는 객체입니다.

초기 값이 알려지지 않은 경우, 일반적으로 값 계산이 아직 완료되지 않았기 때문에 값의 플레이스홀더 역할을 합니다.

Promise의 주요 특징은 다음과 같습니다.

  • Stateful(상태를 가짐): Promise는 다음 세 가지 상태 중 하나를 가집니다.
    • Pending(대기 중) 이행되거나 거부되지 않은 초기 상태입니다.
    • Fulfilled(이행됨) 작업이 성공적으로 완료되었습니다.
    • Rejected(거부됨) 작업이 실패했습니다.
  • Immutable(불변성): Promise가 이행되거나 거부되면 상태가 변경될 수 없습니다. 불변이 되어 결과를 영구적으로 유지며 이는 비동기식 흐름 제어에서 Promise를 안정적으로 만듭니다.
  • Chaining(체이닝): Promise는 연결될 수 있습니다. 즉, 하나의 Promise의 출력이 다른 Promise의 입력으로 사용될 수 있습니다. 이는 .then()성공 또는 .catch()실패 처리를 위해 사용되어 읽기 쉬운 순차적 비동기 작업을 허용합니다. 체이닝은 함수 합성과 비동기식으로 동일합니다.
const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("Success!");
    // You could also reject with a new error on failure.
  }, 1000);
});

promise
  .then((value) => {
    console.log(value); // Success!
  })
  .catch((error) => {
    console.log(error);
  });

JavaScript에서는 async/await 구문을 사용하여 Promise 및 Promise 반환 함수를 동기식인 것처럼 처리할 수 있습니다. 이렇게 하면 비동기 코드를 훨씬 쉽게 읽고 추론할 수 있습니다.

const processData = async () => {
  try {
    const data = await fetchData(); // Promise가 해결될 때까지 대기합니다.
    console.log("Processed:", data); // 데이터를 처리하고 표시합니다.
  } catch (error) {
    console.error("Error:", error); // 오류를 처리합니다.
  }
};

6. TypeScript란 무엇인가?

TypeScript는 Microsoft에서 개발하고 유지 관리하는 JavaScript의 상위 집합입니다.

최근 몇 년 동안 인기가 크게 높아졌으며 JavaScript 개발자라면 결국 TypeScript를 사용해야 할 가능성이 높습니다. 이는 동적 타입 언어인 JavaScript에 정적 타입 지정을 추가합니다. 정적 타이핑은 개발자가 개발 프로세스 초기에 오류를 포착하여 코드 품질과 유지 관리성을 향상시키는 데 도움이 됩니다.

TypeScript의 주요 기능

  • 정적 타이핑: 변수 및 함수 매개변수의 타입을 정의하여 코드 전체에서 일관성을 보장합니다.
  • 향상된 IDE 지원: IDE(통합 개발 환경)는 더 나은 자동 완성, 탐색 및 리팩토링 기능을 제공하여 개발 프로세스를 더욱 효율적으로 만듭니다.
  • 컴파일: TypeScript 코드는 JavaScript로 변환되어 모든 브라우저 또는 JavaScript 환경과 호환됩니다. 이 과정에서 타입 오류가 발견되어 코드를 안정적으로 만듭니다.
  • 인터페이스: 인터페이스는 객체와 함수가 만족해야 하는 추상적인 계약을 명시하는 데 사용됩니다.
  • JavaScript와의 호환성: TypeScript는 기존 JavaScript 코드와 매우 호환됩니다. JavaScript 코드는 점진적으로 TypeScript로 마이그레이션되어 기존 프로젝트의 전환이 원활하게 이루어질 수 있습니다.
interface User {
  id: number;
  name: string;
}

type GetUser = (userId: number) => User;

const getUser: GetUser = (userId) => {
  // 유저 데이터를 데이터베이스나 API에서 가져옵니다.
  return {
    id: userId,
    name: "John Doe",
  };
};

버그에 대한 가장 좋은 방어책은 코드 리뷰, TDD, ESLint와 같은 린트 도구입니다. TypeScript는 이러한 관행을 대체하는 것이 아닙니다. 타입 정확성이 프로그램 정확성을 보장하지 않기 때문입니다. TypeScript는 때때로 다른 모든 품질 측정값이 적용된 후에도 버그를 잡아내기도 합니다. 그러나 TypeScript의 주요 이점은 IDE 지원을 통한 개발자 경험 향상입니다.

7. 웹 컴포넌트란 무엇인가?

웹 컴포넌트는 웹 페이지와 웹 앱에서 사용할 수 있는 새로운 사용자 정의, 재사용 가능한 캡슐화된 HTML 태그를 생성할 수 있는 웹 플랫폼 API 집합입니다.

HTML, CSS, JavaScript와 같은 개방형 웹 기술을 사용하여 구축됩니다. 브라우저의 일부이며 외부 라이브러리나 프레임워크가 필요하지 않습니다.

웹 컴포넌트는 서로 다른 프레임워크를 사용할 수 있는 많은 엔지니어가 있는 대규모 팀에서 특히 유용합니다. 어떤 프레임워크에서나, 또는 프레임워크를 전혀 사용하지 않고도 사용할 수 있는 재사용 가능한 컴포넌트를 만들 수 있습니다. 예를 들어, Adobe의 Spectrum 디자인 시스템은 웹 컴포넌트를 사용하여 구축되었으며, Reract와 같은 인기 있는 프레임워크와 원활하게 통합됩니다.

웹 컴포넌트는 오랫동안 존재해왔지만, 최근 대규모 조직에서 특히 인기가 높아졌습니다. 모든 주요 브라우저에서 지원되며, W3C 표준입니다.

<!-- 간단한 웹 컴포넌트 정의 -->
<script>
  // HTMLElement를 확장하는 클래스 정의
  class SimpleGreeting extends HTMLElement {
    // shadowRoot를 추가하는 생성자 정의
    constructor() {
      super();
      const shadowRoot = this.attachShadow({ mode: "open" });
      // shadowRoot의 innerHTML에 템플릿 리터럴 사용
      shadowRoot.innerHTML = `
        <style>
          /* 스타일 태그를 사용하여 웹 컴포넌트 스타일 지정 */
          p {
            font-family: Arial, sans-serif;
            color: var(--color, black); /* 색상에 CSS 변수 사용 */
          }
        </style>
        <!-- <slot> 요소는 사용자가 제공한 콘텐츠의 자리 표시자입니다. 콘텐츠가 제공되지 않으면 자체 기본 콘텐츠를 표시합니다. -->
        <p><slot>Hello, Web Components!</slot></p>
      `;
    }

    // 관찰 대상 속성에 대한 정적 getter 정의
    static get observedAttributes() {
      return ["color"]; // color 속성을 관찰
    }

    // 속성이 변경될 때의 콜백 함수 정의
    attributeChangedCallback(name, oldValue, newValue) {
      // color 속성이 변경되면 CSS 변수 업데이트
      if (name === "color") {
        this.style.setProperty("--color", newValue);
      }
    }
  }

  // 사용자 지정 요소를 태그 이름과 함께 등록
  customElements.define("simple-greeting", SimpleGreeting);
</script>

<!-- 웹 컴포넌트 사용 -->
<!-- 슬롯을 사용하여 사용자 정의 인사말 메시지 전달 -->
<simple-greeting>Hello, reader!</simple-greeting>
<!-- 속성을 사용하여 사용자 정의 색상 전달 -->
<simple-greeting color="blue">Hello, World!</simple-greeting>

8. Reract Hooks란 무엇인가?

Hooks는 클래스를 작성하지 않고도 상태 및 기타 React 기능을 사용할 수 있게 해주는 함수입니다.

Hook을 사용하면 클래스 메서드를 작성하는 대신 함수를 호출하여 상태, 컨텍스트, 참조 및 구성 요소 수명 주기 이벤트를 사용할 수 있습니다. 함수의 추가적인 유연성을 통해 코드를 더 효과적으로 구성하고, 관련 기능을 단일 Hook 호출로 그룹화하고, 관련 없는 기능을 별도의 함수 호출로 구현하여 분리할 수 있습니다. Hook은 구성 요소 내부에 논리를 구성하는 강력하고 표현력이 풍부한 방법을 제공합니다.

중요한 React Hooks는 다음과 같습니다.

  • useState: 함수형 컴포넌트에 상태를 추가할 수 있게 해줍니다. 상태 변수는 리렌더링 간에 유지됩니다.
  • useEffect: 함수형 컴포넌트에서 사이드 이펙트를 수핼할 수 있게 해줍니다. componentDidMount, componentDidUpdate, componentWillUnmount의 기능을 하나의 함수 호출로 결합하여 클래스 컴포넌트보다 더 나은 코드 구조를 만들고 필요한 코드를 줄입니다.
  • useContext: 함수형 컴포넌트에서 컨텍스트를 사용할 수 있게 해줍니다. 컨텍스트를 사용하면 컴포넌트 트리를 통해 데이터를 전달할 수 있습니다.
  • useRef - 함수형 컴포넌트의 수명 동안 지속되는 수정 가능한 참조를 생성할 수 있게 해줍니다.
  • 사용자 정의(Custom) Hook: 재사용 가능한 로직을 캡슐화하는 데 사용됩니다. 이를 통해 다양한 컴포넌트 간에 로직을 쉽게 공유할 수 있습니다.

Hook 규칙: React 함수의 최상위 수준(루프, 조건 또는 중첩 함수 내부가 아님)에서 사용해야 하며 React 함수형 컴포넌트 또는 사용자 정의 Hook에서만 사용해야 합니다.

Hooks는 생성자에서 메서드를 바인딩해야 하는 필요성, 기능을 여러 수명 주기 메서드로 분할해야 하는 필요성 등 클래스 컴포넌트와 관련된 몇 가지 일반적인 문제를 해결했습니다. 또한 컴포넌트 간에 논리를 더 쉽게 공유하고 컴포넌트 계층 구조를 변경하지 않고도 상태 저장 로직을 재사용할 수 있습니다.

9. React에서 Click Counter를 만드는 방법은?

useState Hook을 사용하여 React에서 Click Counter를 만들 수 있습니다.

import React, { useState } from "react";

const ClickCounter = () => {
  const [count, setCount] = useState(0); // 초깃값을 0으로 설정

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount((count) => count + 1)}>Click me</button>
    </div>
  );
};

export default ClickCounter;

setCount 기존 상태에서 새 값을 파생할 때 항상 최신 상태로 작업할 수 있도록 함수를 전달하는 것이 모범 사례입니다.

10. TDD(Test-driven development)란 무엇인가?

TDD(테스트 중심 개발)는 실제 코드 전에 테스트를 작성하는 소프트웨어 개발 접근 방식입니다.

코드가 지정된 요구 사항을 충족하고 버그가 없는지 확인하도록 설계된 짧고 반복적인 개발 주기를 중심으로 진행됩니다. TDD는 코드 품질을 개선하고, 버그를 줄이고, 개발자 생산성을 높이는 데 중요한 역할을 할 수 있습니다.

개발 팀 생산성의 가장 중요한 측정 방법 중 하나는 배포 빈도입니다. 지속적인 배표의 주요 장애물 중 하나는 변화에 대한 두려움입니다. TDD는 코드가 항상 배포 가능한 상태인지 확인하여 이러한 두려움을 줄이는 데 도움이 됩니다. 이를 통해 새로운 기능과 버그 수정을 더 쉽게 배포할 수 있으며 결과적으로 배포 빈도가 높아집니다.

먼저 테스트를 진행하면 나중에 테스트를 하는 것보다 많은 이점이 있습니다.

  • 코드 커버리지 향상: 테스트를 먼저 작성하면 모든 엣지 케이스를 더 잘 커버할 수 있습니다.
  • 향상된 API 디자인: 테스트를 먼저 작성하면 코드를 작성하기 전에 API 디자인에 대해 생각하게 되므로, 구현 세부 정보가 API로 유출되는 것을 방지하는 데 도움이 됩니다.
  • 버그 감소: 테스트를 먼저 작성하면 버그를 개발 프로세스 초기에 잡을 수 있어 수정이 더 쉽습니다.
  • 더 나은 코드 품질: 테스트를 먼저 작성하면 모듈화되고 느슨하게 결합된 코드를 작성하게 되어 유지 보수 및 재사용이 더 쉬워집니다

TDD의 주요 단계:

  1. 테스트 작성: 해당 기능이 아직 존재하지 않기 때문에 이 테스트는 처음에는 실패합니다.
  2. 구현 코드 작성: 테스트를 통과할 수 있을 만큼만 구현합니다.
  3. 자신감 있게 리팩토링: 테스트가 통과되면 코드를 자신감 있게 리팩토링 할 수 있습니다. 리팩토링은 외부 동작을 변경하지 않고 기존 코드를 재구성하는 과정입니다. 리팩토링의 목적은 코드를 정리하고 가독성을 향상시키며 복잡성을 줄이는 것입니다. 테스트가 있으면 실수를 저지르더라도 테스트 실패로 즉시 알림을 받을 수 있습니다.

반복: 각 기능 요구 사항에 대해 주기가 반복되어 모든 테스트가 계속 통과되는지 확인하면서 점진적으로 소프트웨어를 구축합니다.

도전 과제

  • 러닝 커브: TDD는 개발하는 데 상당한 시간이 걸릴 수 있는 기술이자 규칙입니다. TDD를 6개월 동안 적용하더라도 여전히 TDD가 어렵고 생산성을 방해하는 것처럼 느껴질 수 있습니다. 그러나 2년이 지난 후에는 TDD가 자연스러워지고, 이전보다 더 생산적인 경험을 할 것으로 기대됩니다.
  • 시간 소모: 모든 작은 기능에 대해 테스트를 작성하는 것은 처음에는 시간이 많이 들 수 있지만, 일반적으로 장기적으로는 버그 감소와 더 쉬운 유지보수로 이어집니다.

    가이드의 작성자인 Eric Elliott는 사람들에게 "TDD를 적용할 시간이 없다고 생각한다면 정말로 TDD를 건너뛸 시간이 없다"고 말하고 있습니다.

8번과 9번의 주제는 React에 관한 주제로 "모든" JavaScript 개발자가 알아야 하는 인터뷰 질문이라는 제목에 부합하지 않을 수 있습니다.

이에 대해 본 가이드의 작성자인 Eric Elliott은 해당 가이드는 JavaScript 개발자가 알아야하는 "인터뷰 질문"이기 때문에 전 세계 JavaScript 개발자의 50% 이상이 사용하고 있는 React의 주제이기 때문에 취업 확률이 높아질 것이라고 말하고 있습니다.

0개의 댓글