JavaScript의 세계는 크게 발전했으며 인터뷰의 트렌드 역시 수년에 걸쳐 많은 변화를 겪었습니다. 해당 가이드는 2024년에 모든 JavaScript 개발자가 알아야 하는 10가지 필수 질문들을 제공합니다. 본 가이드는 closure에서부터 TDD까지 다양한 주제를 다루며, 최신 JavaScript 문제에 대처할 수 있는 지식과 자신감을 제공합니다.
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 변수에 반영되며 그 반대도 마찬가지라는 것을 의미합니다. 즉, 동일한 외부 함수에서 선언된 다른 함수들도 변경 사항에 접근할 수 있음을 의미합니다.
클로저의 일반적인 사용 사례는 다음과 같습니다.
- partial application은 기존 함수의 매개변수들 중 일부를 미리 넣어둔 새로운 함수를 만드는 것이고, 만들어진 partial application 함수는 다음 번 호출 시에는 결과를 반환해야 한다.
- currying은 기존 함수의 매개변수를 하나씩 받는 방법이고, 매개변수를 모두 받을 때까지 함수를 반환한다.
캡슐화는 객체 지향 프로그래밍의 핵심 기능입니다.
이를 통해 외부부터 클래스의 구현 세부 사항을 숨길 수 있습니다. JavaScript의 클로저를 사용하면 객체에 대한 비공개 변수를 선언할 수 있습니다.
const createCounter = () => {
let count = 0;
return {
increment: () => ++count,
decrement: () => --count,
getCount: () => count,
};
};
// Currying 함수는 한 번에 하나의 인수를 취합니다.
const add = (a) => (b) => a + b;
// Partial application은 함수의 일부 인수가 적용되었지만, 아직 모든 인수가 적용되지 않은 함수입니다.
const increment = add(1); // Partial application
increment(2); // 3
순수 함수는 함수형 프로그래밍에서 중요한 개념입니다.
순수 함수는 예측 가능하므로 비순수 함수보다 이해, 디버깅, 테스트하기 쉽습니다.
순수 함수는 아래의 두 규칙을 따릅니다.
Deterministic(결정론적): 동일한 입력이 주어지면 항상 동일한 출력 값을 반환합니다.
No side-effect(사이드 이펙트가 없다): 함수 외부의 상태를 변경하지 않습니다.
함수 합성은 두 개 이상의 함수를 조합하여 새로운 함수를 생성하거나 어떤 계산을 수행하는 과정입니다.
예를 들어 (f ∘ g)(x) = f(g(x))
는 f
와 g
를 합성한 함수이며 x
는 f
의 g
의 인수입니다.
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 개발자는 함수 합성을 통해 대규모 컴포넌트 트리를 정리할 수 있습니다. 컴포넌트를 중첩하는 대신, 컴포넌트들을 새로운 고차 컴포넌트로 만들 수 있습니다. 이 고차 함수를 통해 기존 컴포넌트에 새로운 기능을 추가할 수 있습니다.
함수형 프로그래밍은 순수 함수를 주요 구성 단위로 사용하는 프로그래밍 패러다임입니다.
소프트웨어 개발에서 구성은 매우 중요하므로 사실상 모든 프로그래밍 패러다임은 구성 단위의 이름을 따서 명명됩니다.
함수형 프로그래밍은 선언적 프로그래밍 패러다임입니다. 즉, 프로그램은 프로그램을 수행하는 방법이 아닌 수행하는 작업에 따라 작성됩니다. 이는 기능적 프로그램을 명령형 프로그램보다 이해하고 디버깅하고 테스트하기 쉽게 만듭니다. 또한 훨씬 더 간결해지는 경향이 있어 코드 복잡성이 줄어들고 유지 관리가 더 쉬워집니다.
함수형 프로그래밍의 다른 주요 특징은 다음과 같습니다.
순수 함수는 테스트하기 쉽기 때문에 함수형 프로그래밍은 더 나은 테스트 적용 범위와 더 적은 버그로 이어지는 경향이 있습니다.
JavaScript의 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); // 오류를 처리합니다.
}
};
TypeScript는 Microsoft에서 개발하고 유지 관리하는 JavaScript의 상위 집합입니다.
최근 몇 년 동안 인기가 크게 높아졌으며 JavaScript 개발자라면 결국 TypeScript를 사용해야 할 가능성이 높습니다. 이는 동적 타입 언어인 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 지원을 통한 개발자 경험 향상입니다.
웹 컴포넌트는 웹 페이지와 웹 앱에서 사용할 수 있는 새로운 사용자 정의, 재사용 가능한 캡슐화된 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>
Hooks는 클래스를 작성하지 않고도 상태 및 기타 React 기능을 사용할 수 있게 해주는 함수입니다.
Hook을 사용하면 클래스 메서드를 작성하는 대신 함수를 호출하여 상태, 컨텍스트, 참조 및 구성 요소 수명 주기 이벤트를 사용할 수 있습니다. 함수의 추가적인 유연성을 통해 코드를 더 효과적으로 구성하고, 관련 기능을 단일 Hook 호출로 그룹화하고, 관련 없는 기능을 별도의 함수 호출로 구현하여 분리할 수 있습니다. Hook은 구성 요소 내부에 논리를 구성하는 강력하고 표현력이 풍부한 방법을 제공합니다.
중요한 React Hooks는 다음과 같습니다.
useState
: 함수형 컴포넌트에 상태를 추가할 수 있게 해줍니다. 상태 변수는 리렌더링 간에 유지됩니다.useEffect
: 함수형 컴포넌트에서 사이드 이펙트를 수핼할 수 있게 해줍니다. componentDidMount
, componentDidUpdate
, componentWillUnmount의
기능을 하나의 함수 호출로 결합하여 클래스 컴포넌트보다 더 나은 코드 구조를 만들고 필요한 코드를 줄입니다.useContext
: 함수형 컴포넌트에서 컨텍스트를 사용할 수 있게 해줍니다. 컨텍스트를 사용하면 컴포넌트 트리를 통해 데이터를 전달할 수 있습니다.useRef
- 함수형 컴포넌트의 수명 동안 지속되는 수정 가능한 참조를 생성할 수 있게 해줍니다.Hook 규칙: React 함수의 최상위 수준(루프, 조건 또는 중첩 함수 내부가 아님)에서 사용해야 하며 React 함수형 컴포넌트 또는 사용자 정의 Hook에서만 사용해야 합니다.
Hooks는 생성자에서 메서드를 바인딩해야 하는 필요성, 기능을 여러 수명 주기 메서드로 분할해야 하는 필요성 등 클래스 컴포넌트와 관련된 몇 가지 일반적인 문제를 해결했습니다. 또한 컴포넌트 간에 논리를 더 쉽게 공유하고 컴포넌트 계층 구조를 변경하지 않고도 상태 저장 로직을 재사용할 수 있습니다.
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
기존 상태에서 새 값을 파생할 때 항상 최신 상태로 작업할 수 있도록 함수를 전달하는 것이 모범 사례입니다.
TDD(테스트 중심 개발)는 실제 코드 전에 테스트를 작성하는 소프트웨어 개발 접근 방식입니다.
코드가 지정된 요구 사항을 충족하고 버그가 없는지 확인하도록 설계된 짧고 반복적인 개발 주기를 중심으로 진행됩니다. TDD는 코드 품질을 개선하고, 버그를 줄이고, 개발자 생산성을 높이는 데 중요한 역할을 할 수 있습니다.
개발 팀 생산성의 가장 중요한 측정 방법 중 하나는 배포 빈도입니다. 지속적인 배표의 주요 장애물 중 하나는 변화에 대한 두려움입니다. TDD는 코드가 항상 배포 가능한 상태인지 확인하여 이러한 두려움을 줄이는 데 도움이 됩니다. 이를 통해 새로운 기능과 버그 수정을 더 쉽게 배포할 수 있으며 결과적으로 배포 빈도가 높아집니다.
먼저 테스트를 진행하면 나중에 테스트를 하는 것보다 많은 이점이 있습니다.
TDD의 주요 단계:
반복: 각 기능 요구 사항에 대해 주기가 반복되어 모든 테스트가 계속 통과되는지 확인하면서 점진적으로 소프트웨어를 구축합니다.
도전 과제
가이드의 작성자인 Eric Elliott는 사람들에게 "TDD를 적용할 시간이 없다고 생각한다면 정말로 TDD를 건너뛸 시간이 없다"고 말하고 있습니다.
8번과 9번의 주제는 React에 관한 주제로 "모든" JavaScript 개발자가 알아야 하는 인터뷰 질문이라는 제목에 부합하지 않을 수 있습니다.
이에 대해 본 가이드의 작성자인 Eric Elliott은 해당 가이드는 JavaScript 개발자가 알아야하는 "인터뷰 질문"이기 때문에 전 세계 JavaScript 개발자의 50% 이상이 사용하고 있는 React의 주제이기 때문에 취업 확률이 높아질 것이라고 말하고 있습니다.