JavaScript에서 this는 함수가 호출되는 방식에 따라 동적으로 결정되는 독특한 메커니즘을 가지고 있습니다. 이 동적 바인딩은 유연성을 제공하지만, 동시에 개발자가 this의 값을 잘못 예측하여 버그를 만들어내는 가장 흔한 원인이기도 합니다. 콜백 함수, 이벤트 핸들러, 비동기 처리 등 다양한 상황에서 this가 예상과 다르게 바인딩되면, 런타임 에러나 의도하지 않은 동작이 발생합니다. 이 토픽에서는 this 바인딩을 일관되고 예측 가능하게 유지하기 위한 실용적인 코딩 패턴들을 살펴봅니다. 화살표 함수, bind 메서드, self 변수 패턴 등 실무에서 검증된 접근법을 이해하면, this로 인한 혼란을 근본적으로 제거할 수 있습니다.
🎯 핵심 특징
- 화살표 함수의 렉시컬 바인딩: 화살표 함수는 자신만의
this를 생성하지 않고, 정의된 시점의 외부 스코프에서this를 상속받아 콜백이나 중첩 함수에서도 일관된this참조를 보장합니다 bind메서드를 통한 명시적 고정:Function.prototype.bind를 사용하면 함수의this를 특정 객체로 영구적으로 고정할 수 있어, 함수를 다른 컨텍스트로 전달해도 바인딩이 유지됩니다self/that변수 캡처 패턴: 클로저를 활용하여 외부 스코프의this를 일반 변수에 저장하는 클래식한 패턴으로, 화살표 함수 등장 이전부터 사용되어온 레거시 코드의 핵심 관용구입니다- 클래스 필드와 자동 바인딩: 클래스 필드에 화살표 함수를 할당하면 인스턴스 생성 시 자동으로
this가 바인딩되어, 이벤트 핸들러 등록 시 별도의 바인딩 처리가 필요 없습니다 - 패턴 선택의 트레이드오프: 각 패턴은 가독성, 메모리 효율, 프로토타입 활용 가능성 등에서 서로 다른 장단점을 가지며, 상황에 맞는 올바른 패턴 선택이 코드 품질을 결정합니다
💡 실무에서의 영향
this 바인딩 패턴을 정확히 이해하고 적용하면, 프론트엔드 개발에서 가장 빈번하게 발생하는 런타임 에러 중 하나인 TypeError: Cannot read properties of undefined를 사전에 방지할 수 있습니다. React 컴포넌트에서 이벤트 핸들러를 등록하거나, Node.js에서 비동기 콜백을 처리할 때, 적절한 바인딩 패턴을 적용하면 디버깅 시간이 크게 줄어듭니다. 특히 팀 프로젝트에서는 일관된 패턴을 사용함으로써 코드 리뷰 시 this 관련 논쟁을 줄이고, 새로운 팀원도 빠르게 코드를 이해할 수 있게 됩니다. 레거시 코드를 유지보수할 때 self/that 패턴을 인식하고, 새 코드에서는 화살표 함수나 클래스 필드를 활용하는 판단력은 실무에서 즉시 활용 가능한 역량입니다. 또한 각 패턴의 메모리 사용량과 성능 특성을 이해하면, 대규모 애플리케이션에서 수천 개의 컴포넌트 인스턴스가 생성될 때 발생할 수 있는 성능 이슈를 사전에 고려한 설계가 가능해집니다.
핵심 개념
화살표 함수의 렉시컬 this
입문
화살표 함수는 자기만의 this를 만들지 않고, 바깥쪽 코드의 this를 그대로 빌려 쓰는 특별한 함수예요. 왜 이게 중요한지 알아볼까요!
📦 this가 뭔가요? this는 ‘나’라는 뜻이에요. 마치 대화할 때 “나는 배고파”라고 하면 말하는 사람에 따라 ‘나’가 달라지듯이, 프로그래밍에서도 this는 상황에 따라 가리키는 대상이 달라져요.
🏠 집 주소가 바뀌는 문제 일반 함수는 새로운 방에 들어갈 때마다 ‘여기가 내 집이야’라고 새 주소를 받아요. 그래서 방 안의 방으로 들어가면 원래 집 주소를 잊어버리는 문제가 생겨요. 택배를 보내려는데 갑자기 주소가 바뀌면 곤란하겠죠?
🎯 화살표 함수는 어떻게 해결하나요? 화살표 함수는 새 주소를 받지 않아요! 처음 만들어진 곳의 주소를 계속 기억해요. 마치 고향 주소가 적힌 팔찌를 차고 다니는 것처럼, 어디를 가든 원래 주소를 잊지 않아요.
💡 언제 유용한가요? 누군가에게 “나중에 이 일을 대신 해줘”라고 부탁할 때 유용해요. 보통 함수는 부탁받은 사람이 ‘나’를 자기 자신으로 착각하지만, 화살표 함수는 원래 부탁한 사람이 ‘나’라는 것을 정확히 기억해요.
🚨 주의할 점이 있나요? 화살표 함수는 항상 바깥의 this를 쓰기 때문에, 새로운 this가 필요한 상황에서는 오히려 문제가 돼요. 마치 항상 고향 주소만 고집하면 새 집에서 택배를 받을 수 없는 것처럼요.
중급
화살표 함수(Arrow Function)는 자체적인 this 바인딩을 생성하지 않고, 함수가 정의된 시점의 외부 스코프(Lexical Scope)에서 this를 상속받습니다. 이를 렉시컬 바인딩(Lexical Binding)이라고 합니다.
일반 함수와의 핵심 차이
일반 함수는 호출 시점에 this가 결정되지만(동적 바인딩), 화살표 함수는 정의 시점에 this가 결정됩니다(정적 바인딩). 이 차이가 콜백 함수에서 this를 안정적으로 사용할 수 있게 해줍니다.
const timer = {
seconds: 0,
start() {
// 일반 함수: this가 timer가 아닌 전역 객체를 가리킴
setInterval(function () {
this.seconds++; // NaN - this는 window/undefined
}, 1000);
// 화살표 함수: 외부 스코프(start 메서드)의 this를 상속
setInterval(() => {
this.seconds++; // 정상 동작 - this는 timer
}, 1000);
}
};
렉시컬 바인딩의 동작 원리
화살표 함수가 평가될 때, 엔진은 현재 실행 컨텍스트의 this 값을 클로저로 캡처합니다. 이후 화살표 함수가 어떤 방식으로 호출되더라도(메서드 호출, call/apply, 이벤트 핸들러 등) this는 항상 캡처된 값을 유지합니다. call, apply, bind로도 화살표 함수의 this를 변경할 수 없습니다.
const obj = {
value: 42,
getArrow() {
const arrow = () => this.value;
return arrow;
}
};
const arrow = obj.getArrow();
console.log(arrow()); // 42
console.log(arrow.call({ value: 99 })); // 42 - call로도 변경 불가
심화
화살표 함수의 렉시컬 this 바인딩은 ECMAScript 명세에서 일반 함수와 근본적으로 다른 내부 슬롯 구조를 통해 구현되며, 이는 함수 객체 생성 시점의 환경 레코드(Environment Record) 참조 메커니즘에 기반합니다.
ECMAScript 명세의 [[ThisMode]] 내부 슬롯
ECMAScript 2023 명세 10.2.1절(ECMAScript Function Objects)에 따르면, 모든 함수 객체는 [[ThisMode]] 내부 슬롯(Internal Slot, 엔진이 내부적으로 관리하는 메타데이터)을 가집니다. 이 슬롯은 세 가지 값 중 하나를 가질 수 있습니다:
- lexical: 화살표 함수에 해당. 자체
this바인딩을 생성하지 않음 - strict: 엄격 모드 함수.
this가undefined로 초기화될 수 있음 - global: 비엄격 모드 함수.
this가 전역 객체로 대체될 수 있음
화살표 함수의 [[ThisMode]]가 lexical로 설정되면, OrdinaryCallBindThis 추상 연산(Abstract Operation, 명세에서 정의하는 내부 알고리즘)이 호출되지 않습니다. 즉, this 바인딩 단계 자체가 생략됩니다.
실행 컨텍스트와 환경 레코드의 상호작용
화살표 함수가 this를 참조할 때, 엔진은 현재 함수의 환경 레코드에서 this를 찾지 않고, [[OuterEnv]] 참조(외부 환경 레코드를 가리키는 내부 링크)를 따라 상위 스코프 체인을 탐색합니다. 이 과정은 ResolveThisBinding() 추상 연산을 통해 수행되며, HasThisBinding()이 false를 반환하는 환경 레코드를 건너뛰고 true를 반환하는 가장 가까운 환경 레코드에서 this 값을 가져옵니다.
V8 엔진의 최적화 전략
V8 엔진은 화살표 함수의 this 참조를 최적화하기 위해 컨텍스트 체인(Context Chain) 압축을 수행합니다. 화살표 함수가 외부 함수의 this만 참조하고 다른 변수를 캡처하지 않는 경우, 별도의 클로저 컨텍스트 할당 없이 부모 컨텍스트의 this 슬롯을 직접 참조하도록 최적화합니다. 이는 TurboFan 컴파일러(V8의 최적화 JIT 컴파일러)의 Escape Analysis(탈출 분석, 객체가 함수 외부로 노출되는지 분석하는 기법) 단계에서 결정됩니다.
bind 메서드를 통한 명시적 바인딩
입문
bind는 함수에게 “너는 항상 이 주인을 따라야 해”라고 약속시키는 방법이에요. 한번 약속하면 누가 불러도 원래 주인을 기억해요!
🎀 리본 달기 여러분이 강아지 인형에 이름표 리본을 달아준다고 생각해보세요. 리본에 “나는 수아의 강아지”라고 적혀 있으면, 이 인형을 친구에게 빌려줘도 리본의 내용은 바뀌지 않아요. bind도 마찬가지로 함수에 ‘소유자 리본’을 달아주는 거예요.
📌 왜 필요한가요? 함수를 다른 곳에 넘겨줄 때 문제가 생겨요. 마치 반 친구에게 “우리 엄마한테 전화해줘”라고 부탁했는데, 친구가 자기 엄마한테 전화하는 것과 비슷해요. bind를 쓰면 “이 번호로만 전화해”라고 번호를 미리 저장해 둘 수 있어요.
🔗 어떻게 동작하나요? bind를 사용하면 원래 함수를 복사한 새 함수가 만들어져요. 이 새 함수는 어떤 상황에서 호출되든 항상 지정된 주인을 기억해요. 원본 함수는 변하지 않고, 리본이 달린 복사본이 생기는 거예요.
💡 실생활로 이해하기 TV 리모컨에 ‘우리집 TV 전용’이라고 설정해둔다고 상상해보세요. 이 리모컨을 이웃집에 가져가도 우리집 TV만 작동해요. bind도 마찬가지로 함수가 항상 특정 대상에게만 작동하도록 설정하는 거예요.
중급
Function.prototype.bind는 함수의 this를 특정 객체로 영구적으로 고정한 새로운 함수를 반환합니다. 원본 함수를 수정하지 않고, 바인딩된 새 함수(Bound Function)를 생성합니다.
bind의 핵심 특성
- 원본 함수를 변경하지 않고 새로운 바운드 함수를 반환
- 바운드 함수의
this는call이나apply로도 변경 불가 - 부분 적용(Partial Application)에도 활용 가능: 인자를 미리 고정
class Logger {
constructor(prefix) {
this.prefix = prefix;
}
log(message) {
console.log(`${this.prefix}: ${message}`);
}
}
const logger = new Logger('[App]');
// 문제: this가 유실됨
setTimeout(logger.log, 1000, 'hello');
// undefined: hello (this가 전역 객체)
// 해결: bind로 this 고정
setTimeout(logger.log.bind(logger), 1000, 'hello');
// [App]: hello
bind와 call/apply의 차이
call과 apply는 함수를 즉시 실행하면서 this를 지정하지만, bind는 실행하지 않고 this가 고정된 새 함수를 반환합니다. 따라서 콜백 함수처럼 나중에 실행될 함수의 this를 고정할 때 bind가 적합합니다.
function multiply(a, b) {
return a * b;
}
// 첫 번째 인자를 2로 고정
const double = multiply.bind(null, 2);
console.log(double(5)); // 10
console.log(double(10)); // 20
심화
bind는 ECMAScript 명세에서 Bound Function Exotic Object(바운드 함수 이색 객체, 일반 함수와 다른 내부 구조를 가진 특수 함수 객체)를 생성하며, 이는 일반 함수 객체와 근본적으로 다른 내부 슬롯 구조를 가집니다.
ECMAScript 명세의 BoundFunctionCreate 추상 연산
ECMAScript 2023 명세 10.4.1절에 따르면, bind 호출 시 BoundFunctionCreate 추상 연산이 실행됩니다. 생성된 Bound Function Exotic Object는 세 가지 핵심 내부 슬롯을 가집니다:
[[BoundTargetFunction]]: 래핑된 원본 대상 함수(Target Function)에 대한 참조[[BoundThis]]:this로 사용될 고정된 값. 이후 어떤 호출 방식을 사용해도 이 값이 전달됨[[BoundArguments]]: 부분 적용(Partial Application)을 위해 미리 고정된 인자 목록
바운드 함수의 [[Call]] 내부 메서드가 호출되면, 전달된 thisArgument를 무시하고 [[BoundThis]]를 사용합니다. 이것이 call이나 apply로도 바운드 함수의 this를 변경할 수 없는 이유입니다.
이중 바인딩과 체이닝 동작
bind를 연속으로 호출하면 바운드 함수가 다시 래핑됩니다. 첫 번째 bind로 생성된 바운드 함수에 두 번째 bind를 호출하면, 새로운 바운드 함수의 [[BoundTargetFunction]]은 첫 번째 바운드 함수를 가리키지만, 최종 호출 시 첫 번째 바운드 함수의 [[BoundThis]]가 우선합니다. 따라서 두 번째 이후의 bind에서 지정한 this는 사실상 무시됩니다.
V8 엔진의 바운드 함수 최적화
V8은 바운드 함수 호출 시 발생하는 간접 호출(Indirect Call) 오버헤드를 줄이기 위해 인라이닝 최적화를 수행합니다. TurboFan 컴파일러는 바운드 함수의 [[BoundTargetFunction]]이 단형(Monomorphic, 항상 동일한 함수를 가리키는 상태)인 경우, 간접 호출을 제거하고 대상 함수를 직접 호출하는 코드를 생성합니다. 그러나 [[BoundArguments]]가 비어있지 않은 경우(부분 적용 사용 시), 인자 배열의 결합 비용이 추가되어 일반 함수 호출 대비 약 10-15%의 오버헤드가 발생할 수 있습니다(V8 v12.0 기준 벤치마크).
self/that 변수 캡처 패턴
입문
self 패턴은 this를 다른 이름으로 미리 저장해두는 방법이에요. 마치 중요한 전화번호를 메모장에 적어두는 것처럼요!
📝 메모장에 적어두기 여러분이 친구 집에 놀러 가기 전에 우리 집 주소를 메모장에 적어둔다고 생각해보세요. 친구 집에서 택배를 보내야 할 때, 메모장을 보면 우리 집 주소를 바로 알 수 있어요. self 패턴도 마찬가지로 this의 값을 다른 변수에 ‘메모’해 두는 거예요.
❓ 왜 이런 방법이 필요했나요? 옛날에는 화살표 함수가 없었어요. 그래서 함수 안에 함수가 있으면 this가 자꾸 바뀌어서 원래 가리키던 것을 잃어버렸어요. 개발자들이 이 문제를 해결하려고 “그럼 미리 적어두자!”라는 아이디어를 낸 거예요.
🔍 어떻게 작동하나요? 바깥쪽 함수에서 this를 self라는 이름의 변수에 저장해요. 안쪽 함수에서는 this 대신 self를 사용하면 항상 원래 값을 쓸 수 있어요. self는 일반 변수이기 때문에 함수가 바뀌어도 값이 유지되거든요.
📚 요즘도 쓰나요? 새로 작성하는 코드에서는 화살표 함수가 더 깔끔하기 때문에 잘 안 써요. 하지만 오래된 코드에서는 아직도 많이 보이기 때문에, 이 패턴을 읽고 이해할 수 있는 것이 중요해요.
중급
self/that 패턴은 클로저(Closure)를 활용하여 외부 스코프의 this를 일반 변수에 저장하는 기법입니다. ES6 이전에 콜백 내에서 외부 this를 유지하기 위해 널리 사용되었습니다.
패턴의 핵심 원리
일반 변수는 this와 달리 호출 방식에 영향을 받지 않습니다. 클로저를 통해 외부 스코프의 변수를 참조할 수 있으므로, this를 일반 변수에 저장하면 내부 함수에서도 안전하게 접근할 수 있습니다.
function Counter() {
var self = this; // this를 일반 변수에 저장
self.count = 0;
setInterval(function () {
// this는 전역 객체를 가리키지만
// self는 클로저를 통해 Counter 인스턴스를 유지
self.count++;
console.log(self.count);
}, 1000);
}
변수 이름 관례
실무에서는 self, that, _this 등 다양한 이름이 사용됩니다. 팀이나 프로젝트의 코딩 컨벤션에 따라 통일하는 것이 중요합니다. jQuery 시대에는 self가 가장 흔했고, TypeScript 초기에는 _this가 컴파일 결과에 사용되었습니다.
// Before (self 패턴)
function Counter() {
var self = this;
self.count = 0;
setInterval(function () {
self.count++;
}, 1000);
}
// After (화살표 함수)
function Counter() {
this.count = 0;
setInterval(() => {
this.count++; // 렉시컬 this로 자연스럽게 해결
}, 1000);
}
심화
self/that 패턴은 JavaScript의 클로저 메커니즘과 this의 동적 바인딩 특성 간의 근본적 차이를 활용한 우회 기법이며, ECMAScript 명세의 환경 레코드 참조 체계에서 this 바인딩과 일반 변수 바인딩이 서로 다른 메커니즘으로 해석(Resolve)되는 점에 기반합니다.
일반 변수 vs this의 해석 경로 차이
ECMAScript 명세에서 일반 식별자(self, that 등)는 ResolveBinding 추상 연산(Abstract Operation)을 통해 현재 렉시컬 환경(Lexical Environment)의 환경 레코드 체인을 순서대로 탐색하여 해석됩니다. 반면, this 키워드는 ResolveThisBinding 추상 연산을 사용하며, HasThisBinding()이 true를 반환하는 가장 가까운 환경 레코드를 찾습니다. 일반 함수는 항상 HasThisBinding()이 true이므로, 내부 함수의 this가 외부 함수의 this를 가리는(Shadowing) 현상이 발생합니다. self 패턴은 this를 일반 변수로 변환함으로써, HasThisBinding 검사를 우회하고 일반적인 스코프 체인 탐색 경로를 사용하게 합니다.
TypeScript 컴파일러의 _this 변환
TypeScript 컴파일러(tsc)는 ES5 이하 대상으로 컴파일할 때 화살표 함수를 self 패턴으로 변환합니다. 구체적으로, 화살표 함수 내에서 this를 참조하면 외부 함수 시작부에 var _this = this;를 삽입하고, 화살표 함수 내부의 모든 this를 _this로 교체합니다. 이는 TypeScript의 downlevelCompilation 과정에서 transformES2015 변환기가 처리하며, _this 변수명 충돌 시 _this_1, _this_2 등으로 자동 증분합니다.
메모리 및 GC 관점
self 변수는 클로저가 참조하는 외부 변수 중 하나이므로, 클로저가 살아있는 한 GC(Garbage Collector, 더 이상 사용되지 않는 메모리를 자동으로 회수하는 엔진 구성요소)에 의해 수거되지 않습니다. V8의 Orinoco GC는 클로저가 참조하는 변수만 선별적으로 유지하지만, self가 객체 전체를 참조하면 해당 객체와 연결된 모든 프로퍼티가 GC 루트에 포함됩니다. 이는 대규모 객체를 self로 캡처할 때 의도하지 않은 메모리 유지(Memory Retention)를 유발할 수 있습니다.
클래스 필드와 자동 바인딩 패턴
입문
클래스 필드에 화살표 함수를 넣으면, 객체가 만들어질 때 자동으로 this가 고정돼요. 마치 이름표가 붙은 채로 태어나는 것과 같아요!
🏷️ 태어날 때부터 붙는 이름표 사람이 태어나면 출생 신고서에 부모 이름이 적혀있죠? 클래스 필드의 화살표 함수도 마찬가지예요. 객체가 만들어지는 순간 “나는 이 객체의 것”이라는 이름표가 자동으로 붙어요.
🎮 버튼과 리모컨 게임 컨트롤러의 버튼을 생각해보세요. 각 버튼은 만들어질 때부터 ‘이 컨트롤러의 버튼’으로 정해져 있어요. 버튼을 떼어서 다른 컨트롤러에 붙여도, 원래 컨트롤러의 신호를 보내요. 클래스 필드의 화살표 함수도 이렇게 동작해요.
❓ 왜 편리한가요? 원래는 버튼을 만들 때마다 “이 버튼은 이 리모컨 전용이야”라고 일일이 설정해야 했어요. 하지만 이 패턴을 쓰면 처음부터 전용으로 만들어지기 때문에 추가 설정이 필요 없어요.
⚠️ 단점도 있나요? 모든 객체마다 함수를 따로 만들기 때문에, 같은 종류의 객체를 아주 많이 만들면 메모리를 더 많이 사용해요. 마치 모든 학생에게 똑같은 내용의 프린트물을 각각 인쇄해서 나눠주는 것과 같아서, 한 장을 교실에 붙여놓는 것보다 종이가 많이 들어요.
중급
클래스 필드(Class Field)에 화살표 함수를 할당하면, 생성자에서 this.method = this.method.bind(this)를 수동으로 호출하지 않아도 인스턴스에 자동으로 바인딩된 메서드가 생성됩니다.
동작 원리
클래스 필드는 사실 생성자(constructor) 내부에서 실행되는 것과 동일하게 동작합니다. 따라서 화살표 함수가 클래스 필드로 정의되면, 인스턴스 생성 시점에 화살표 함수가 평가되고, 이때의 this(인스턴스)가 렉시컬로 캡처됩니다.
// 방법 1: 생성자에서 bind
class ButtonOld {
constructor() {
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
console.log('clicked:', this);
}
}
// 방법 2: 클래스 필드 화살표 함수 (권장)
class ButtonNew {
handleClick = () => {
console.log('clicked:', this);
};
}
// 두 방법 모두 콜백에서 안전
const btn = new ButtonNew();
document.addEventListener('click', btn.handleClick); // this === btn
프로토타입 vs 인스턴스 메서드
일반 메서드는 프로토타입에 한 번 정의되어 모든 인스턴스가 공유하지만, 클래스 필드 화살표 함수는 인스턴스마다 별도로 생성됩니다. 이는 메모리 사용량에 영향을 줄 수 있으므로, 바인딩이 필요 없는 메서드는 일반 메서드로 정의하는 것이 효율적입니다.
class Example {
// 프로토타입에 1번 정의 (공유)
sharedMethod() { return this; }
// 인스턴스마다 새로 생성 (개별)
boundMethod = () => { return this; };
}
const a = new Example();
const b = new Example();
a.sharedMethod === b.sharedMethod; // true - 같은 함수
a.boundMethod === b.boundMethod; // false - 별개의 함수
심화
클래스 필드의 화살표 함수 패턴은 ECMAScript 명세의 클래스 필드 초기화 시맨틱과 화살표 함수의 렉시컬 this 바인딩이 결합된 결과이며, TC39 Class Fields 제안(proposal-class-fields)의 명세화 과정에서 의도적으로 설계된 동작입니다.
ECMAScript 명세의 DefineField 시맨틱
ECMAScript 2022 명세 15.7.14절(ClassDefinitionEvaluation)에 따르면, 클래스 필드 초기화식(Initializer)은 인스턴스 생성 시 InitializeInstanceElements 추상 연산 내에서 평가됩니다. 이때 각 필드의 초기화식은 인스턴스를 this로 하는 실행 컨텍스트에서 평가되므로, 화살표 함수의 [[ThisMode]]: lexical 특성에 의해 해당 인스턴스가 자동으로 캡처됩니다.
핵심 순서:
new Constructor()호출 시 빈 인스턴스 객체 할당- 생성자 실행 전, 클래스 필드 초기화식 순차 평가
- 화살표 함수의
[[ThisMode]]가 lexical이므로 현재 실행 컨텍스트의this(인스턴스)를 캡처 - DefineField가 인스턴스의 own property로 함수를 설정
프로토타입 메서드와의 메모리 모델 차이
프로토타입 메서드는 Constructor.prototype에 한 번 정의되어 모든 인스턴스가 [[Prototype]] 체인을 통해 공유합니다. 반면 클래스 필드 화살표 함수는 인스턴스마다 새로운 함수 객체를 생성하여 own property로 설정합니다. n개의 인스턴스가 있을 때, 프로토타입 메서드는 O(1) 메모리를 사용하지만, 클래스 필드 화살표 함수는 O(n) 메모리를 소비합니다. V8에서 각 함수 객체는 SharedFunctionInfo(함수의 바이트코드를 공유하는 구조체)를 공유하므로 코드 자체는 중복되지 않지만, 클로저 컨텍스트와 함수 객체 헤더(약 56바이트, V8 v12.0 기준)는 인스턴스마다 할당됩니다.
React 클래스 컴포넌트에서의 활용과 Hooks로의 전환
React 클래스 컴포넌트 시대에 이벤트 핸들러 바인딩 문제의 사실상 표준(de facto standard) 해결책이었습니다. Babel의 @babel/plugin-transform-class-properties가 이 패턴을 지원하면서 React 커뮤니티에서 폭발적으로 보급되었습니다. 현재 React Hooks 기반의 함수형 컴포넌트에서는 클래스 자체를 사용하지 않으므로 이 패턴의 필요성이 크게 감소했지만, Web Components, Lit, Angular 등 클래스 기반 프레임워크에서는 여전히 핵심 패턴으로 사용됩니다.