화살표 함수와 this
일반 함수의 호출 방식에 따라 달라지는 this와 상위 스코프를 캡처하는 화살표 함수의 바인딩을 타입과 함께 구분합니다.
자바스크립트에서 this 키워드는
개발자가 가장 자주 헷갈리는 지점 중 하나입니다.
this 값이 함수 선언이 아니라 호출 방식에 따라 동적으로 결정되기 때문입니다.
ES2015(ES6)의 화살표 함수(Arrow Functions)는
이 this 바인딩 규칙을 크게 바꿨고,
타입스크립트에서도 이 차이를 정확히 이해하는 것이 중요합니다.
이번 절에서는 화살표 함수의 기본적인 사용법을 다시 한번 살펴보고, 특히 this 바인딩 규칙이 어떻게 달라지는지, 그리고 이것이 타입스크립트에서 어떤 의미를 가지는지 자세히 알아보겠습니다.
화살표 함수 기본 문법 복습
화살표 함수는 기존의 function 키워드 함수보다 더 간결한 문법으로 함수를 정의할 수 있게 해줍니다.
// 1. 기본 화살표 함수 (매개변수 2개, 반환 값 존재)
const add = (a: number, b: number): number => {
return a + b;
};
console.log(add(5, 3)); // 8
// 2. 단일 매개변수 (타입 표기가 있으므로 괄호 필요)
const square = (num: number): number => num * num;
console.log(square(4)); // 16
// 3. 매개변수 없음 (빈 괄호 필요)
const sayHello = (): string => "안녕하세요!";
console.log(sayHello()); // 안녕하세요!
// 4. 객체 리터럴 반환 (괄호로 감싸야 함)
const createPoint = (x: number, y: number) => ({ x: x, y: y });
console.log(createPoint(1, 2)); // { x: 1, y: 2 }화살표 함수는 특히 콜백 함수나 짧은 함수를 정의할 때 유용하며, 코드를 훨씬 간결하고 읽기 쉽게 만듭니다.
this 바인딩의 차이점
this 키워드는 함수가 실행될 때 함수의 실행 컨텍스트(Execution Context)에 따라 결정됩니다.
이 this 바인딩 방식에서 화살표 함수와 일반 함수는 결정적인 차이를 보입니다.
-
일반 함수 (
function키워드 함수):this는 함수가 어떻게 호출되었는지(호출 방식)에 따라 동적으로 결정됩니다.- 객체의 메서드로 호출될 때:
this는 해당 객체를 가리킵니다. - 단독으로 호출될 때 (엄격 모드가 아닐 경우):
this는 전역 객체(브라우저에서는window, Node.js에서는global)를 가리킵니다. 엄격 모드('use strict')에서는undefined를 가리킵니다. new키워드와 함께 생성자 함수로 호출될 때:this는 새로 생성된 인스턴스를 가리킵니다.call(),apply(),bind()메서드를 사용할 때:this는 명시적으로 바인딩된 객체를 가리킵니다.
class TraditionalGreeter { greeting: string = "Hello"; sayGreeting(name: string) { console.log(`${this.greeting}, ${name}!`); } // 메서드를 콜백으로 전달할 때 문제가 발생할 수 있습니다. sayGreetingLater() { setTimeout(function() { // 이 일반 함수의 this는 타이머 호스트의 호출 규칙을 따르며 tg가 아닙니다. // 브라우저와 Node.js의 receiver는 다를 수 있고 strict/noImplicitThis 설정도 확인해야 합니다. console.log(`${this.greeting}, world!`); }, 100); } } const tg = new TraditionalGreeter(); tg.sayGreeting("TypeScript"); // Hello, TypeScript! // tg.sayGreetingLater(); // tg의 greeting을 유지하지 않는 예시이며 결과·진단은 호스트와 검사 설정에 따릅니다.이 예시는 일반 함수 콜백이
tg의this를 캡처하지 않는 문제를 보여 줍니다. 타이머가 전달하는 receiver는 실행 호스트에 따라 다르며,noImplicitThis설정에서는 명시되지 않은this타입도 진단 대상입니다. - 객체의 메서드로 호출될 때:
-
화살표 함수: 화살표 함수는 자신만의
this바인딩을 가지지 않습니다.대신, 함수가 선언된 스코프(Lexical Scope)의
this값을 상속(Lexical this) 받습니다.즉, 화살표 함수 내부의
this는 항상 자신을 감싸는(바로 바깥) 일반 함수 또는 전역 스코프의this와 동일합니다.class ArrowGreeter { greeting: string = "Hello"; sayGreetingLater() { setTimeout(() => { // 화살표 함수이므로, sayGreetingLater 메서드의 this를 상속받습니다. // 따라서 여기서 this는 ArrowGreeter 인스턴스를 가리킵니다. console.log(`${this.greeting}, world!`); }, 100); } } const ag = new ArrowGreeter(); ag.sayGreetingLater(); // 약 100ms 후: Hello, world!ArrowGreeter클래스의sayGreetingLater메서드 안에서 사용된 화살표 함수는sayGreetingLater메서드의this를 그대로 상속받습니다. 덕분에this.greeting이 올바르게Hello값을 참조할 수 있습니다. 이런this의 자동 바인딩 특성 때문에 화살표 함수는 이벤트 핸들러나 콜백 함수에서 특히 편리합니다.
아래 다이어그램은 콜백과 메서드에서 this를 어떻게 다룰지 선택하는 흐름을 보여줍니다.
THIS · CALLBACK · IDENTITY
receiver와 함수 identity로 this 전략을 고른다
호출자가 receiver를 제공하는지, 메서드를 값으로 전달하는지, 같은 함수 참조가 필요한지부터 묻는다.
- receiver와 함께 직접
receiver와 함께 직접 호출하면 prototype method가 단순하다.
- 전달할 기존 메서드는
전달할 기존 메서드는 한 번 bind하고 참조를 보관한다.
- lexical this가 필요하면
lexical this가 필요하면 wrapper 또는 class arrow field를 쓴다.
- 호출자가 receiver를 제공하는
호출자가 receiver를 제공하는 API는 this parameter로 정적 검사한다.
화살표 함수 타입 표기만으로 구현의 this가 lexical이 되지는 않는다.
타입스크립트에서 this 타입 명시하기
타입스크립트는 함수의 this가 어떤 타입을 가리킬지 명시적으로 지정할 수 있는 기능을 제공합니다.
이는 특히 라이브러리를 만들거나 복잡한 콜백 패턴에서 this의 타입을 명확히 하고 싶을 때 유용합니다.
this 매개변수는 함수 매개변수 목록의 가장 첫 번째에 위치하며, 실제 자바스크립트 코드로는 컴파일되지 않습니다.
interface MyButton {
label: string;
onClick: (this: MyButton, event: Event) => void; // this가 MyButton 타입임을 명시
}
class ButtonHandler {
buttonLabel: string = "클릭 미";
// 이벤트 핸들러 함수
// 첫 번째 매개변수로 this: ButtonHandler를 명시하여
// 이 함수의 this가 ButtonHandler 인스턴스임을 알려줍니다.
handleClick(this: ButtonHandler, event: Event) {
console.log(`버튼 '${this.buttonLabel}'이 클릭되었습니다.`);
console.log(`이벤트 타입: ${event.type}`);
}
}
const handler = new ButtonHandler();
// 실제로 HTML 버튼에 연결되는 상황을 가정
// document.getElementById('myButton').addEventListener('click', handler.handleClick);
// 가상 호출:
// 만약 handler.handleClick이 버튼의 컨텍스트에서 호출되었다면 (DOM 이벤트)
// handleClick은 ButtonHandler의 this를 요구하므로 DOM 요소 receiver와 계약이 다릅니다.
// 올바르게 this를 바인딩하여 호출하는 방법
const boundHandleClick = handler.handleClick.bind(handler);
// boundHandleClick(new Event('click')); // 이 경우 this는 handler 인스턴스
// ---
// 다른 예시: 객체 리터럴 내에서 this 타입 명시
interface Calculator {
value: number;
add(this: Calculator, num: number): void;
}
const calc: Calculator = {
value: 0,
add(num) { // 여기서 this는 Calculator 타입으로 추론됩니다.
this.value += num;
},
};
calc.add(10);
console.log(calc.value); // 10this: MyButton 또는 this: ButtonHandler와 같이 this 매개변수를 선언하면, 타입스크립트 컴파일러는 해당 함수가 호출될 때 this의 타입이 일치하는지 검사해줍니다.
이는 특히 this의 컨텍스트가 중요하거나 복잡한 상황에서 코드의 안정성을 크게 향상시킵니다.
화살표 함수와 this 타입 명시의 조합
화살표 함수는 렉시컬 this를 사용하며 별도의 this 매개변수를 선언할 수 없습니다.
인터페이스의 (message: string) => void는 함수 타입 표기일 뿐 구현을 화살표 함수로 바꾸지 않습니다. 아래 클래스는 생성자에서 실제 화살표 함수를 할당하므로 인스턴스의 this를 캡처합니다.
interface Logger {
logLevel: string;
// 화살표 함수 문법으로 콜백 시그니처 정의
logMessage: (message: string) => void;
}
class MyLogger implements Logger {
logLevel: string = "INFO";
logMessage: (message: string) => void;
constructor() {
// 화살표 함수를 사용하여 this를 MyLogger 인스턴스에 바인딩
this.logMessage = (message: string) => {
console.log(`[${this.logLevel}] ${message}`);
};
}
}
const logger = new MyLogger();
logger.logMessage("데이터 로드 완료."); // [INFO] 데이터 로드 완료.
// 만약 MyLogger의 logMessage가 일반 함수였다면,
// constructor에서 this를 바인딩하는 과정이 필요했을 것입니다.
/*
class MyLoggerWithTraditionalFunction implements Logger {
logLevel: string = "INFO";
logMessage: (message: string) => void;
constructor() {
this.logMessage = function(message: string) {
console.log(`[${this.logLevel}] ${message}`); // 여기서 this는 MyLogger 인스턴스가 아닐 수 있음
}.bind(this); // 명시적으로 bind를 해줘야 합니다.
}
}
*/화살표 함수 덕분에 this 바인딩에 대한 고민을 줄이고 더 간결하고 직관적인 코드를 작성할 수 있게 됩니다.
화살표 함수와 this의 관계는 자바스크립트 및 타입스크립트에서 매우 중요한 개념입니다.
화살표 함수가 렉시컬 this를 가진다는 점을 명확히 이해하면 콜백 함수나 비동기 코드에서 this 컨텍스트를 유지하는 데 큰 도움이 됩니다.
필요한 경우에는 타입스크립트의 this 타입 명시 기능을 활용하여 코드의 안정성을 더욱 높일 수 있습니다.
이로써 3장 함수와 타입에 대한 모든 내용을 마쳤습니다.