
자바스크립트의 this는 함수가 정의된 위치보다 어떻게 호출됐는지에 따라 결정되는 경우가 많습니다. 객체 메서드를 변수에 담아 전달했더니 this가 사라지거나, 콜백을 화살표 함수로 바꾸자 동작이 달라지는 이유도 여기에 있습니다. class 문법을 사용해도 내부적으로 프로토타입과 함수 호출 규칙이 작동합니다. 호출 방식, 화살표 함수의 어휘적 this, 프로토타입 공유를 함께 이해하면 간헐적인 컨텍스트 버그를 줄일 수 있습니다.
일반 함수의 this는 호출부가 결정한다
user.printName()처럼 점 앞의 객체를 통해 호출하면 메서드 안의 this는 user가 됩니다. 하지만 const print = user.printName; print()처럼 함수를 분리하면 원래 객체 정보가 함께 따라가지 않습니다. 엄격 모드와 ES 모듈에서는 this가 undefined가 될 수 있습니다. 메서드를 이벤트 처리기나 다른 라이브러리에 전달할 때 이 차이를 확인해야 합니다.
call, apply, bind의 역할
call과 apply는 함수를 즉시 실행하면서 this를 지정합니다. call은 인자를 나열하고 apply는 배열 형태로 전달합니다. bind는 지정된 this를 사용하는 새 함수를 반환하므로 콜백으로 넘길 때 유용합니다. render마다 bind를 호출하면 매번 새 함수가 생길 수 있어 이벤트 해제와 성능에 영향을 줄 수 있으므로 생성 시 한 번 바인딩해 보관합니다.
핵심 원칙: 메서드는 객체에서 떼어내는 순간 호출 컨텍스트를 잃을 수 있으므로 콜백 전달 시 bind 또는 명시적인 래퍼를 사용해야 합니다.
화살표 함수에는 자체 this가 없다
화살표 함수는 자신만의 this를 만들지 않고 정의된 바깥 범위의 this를 사용합니다. 클래스 메서드 안의 콜백에서 인스턴스를 참조할 때 편리합니다. 반대로 객체 리터럴의 메서드를 화살표로 정의하면 객체 자신을 this로 받지 못합니다. 생성자 함수로도 사용할 수 없으며 arguments 객체도 자체적으로 제공하지 않습니다.
선택 기준
- 객체의 동적 호출자를 this로 써야 하면 일반 메서드를 사용합니다.
- 바깥 this를 유지하는 짧은 콜백에는 화살표 함수를 고려합니다.
- 생성자와 프로토타입 메서드는 일반 함수 규칙을 따릅니다.
- this가 필요 없다면 매개변수로 의존성을 명시하는 편이 단순합니다.
class 문법과 프로토타입
class로 선언한 메서드는 각 인스턴스에 복사되지 않고 prototype에 공유됩니다. 따라서 많은 인스턴스를 만들 때 메모리 측면에서 유리합니다. 반면 클래스 필드에 화살표 함수를 정의하면 인스턴스마다 새로운 함수가 생성되지만 콜백 전달 시 this가 고정되는 장점이 있습니다. 모든 메서드를 화살표 필드로 만들기보다 사용 방식과 인스턴스 수를 기준으로 선택합니다.
상속보다 조합을 먼저 고려하기
extends와 super를 사용하면 상속을 구현할 수 있지만 계층이 깊어지면 어느 클래스에서 상태가 변경되는지 찾기 어렵습니다. 재사용할 기능을 작은 객체와 함수로 만들고 필요한 클래스에 주입하는 조합 방식이 유연한 경우가 많습니다. 상속은 명확한 is-a 관계와 안정된 계약이 있을 때 제한적으로 사용합니다.
private 필드와 캡슐화
#balance처럼 #으로 시작하는 필드는 클래스 외부에서 직접 접근할 수 없는 실제 private 필드입니다. 밑줄 접두사는 관례일 뿐 접근을 막지 않습니다. private 필드는 서브클래스에서도 직접 접근할 수 없으므로 확장 포인트가 필요하면 보호된 동작을 공개 메서드로 설계합니다. 직렬화와 디버깅 도구가 private 값을 자동 포함하지 않는다는 점도 확인합니다.
getter와 setter의 숨은 비용
getter는 속성처럼 보이지만 함수를 실행합니다. 네트워크 요청이나 무거운 계산을 getter에 넣으면 단순한 값 접근이 예상치 못한 비용과 부수 효과를 만듭니다. getter는 빠르고 결정적인 파생 값에 사용하고 비동기 작업은 명시적인 메서드로 제공합니다. setter에서 광범위한 상태를 변경하기보다 유효성 검사와 업무 의도가 드러나는 메서드를 고려합니다.
이벤트 리스너 해제 문제
element.addEventListener('click', this.handle.bind(this))를 호출한 뒤 removeEventListener에서 다시 bind하면 서로 다른 함수 객체라 해제되지 않습니다. 바인딩한 함수를 필드에 저장하고 등록과 해제에 같은 참조를 사용합니다. 클래스 인스턴스가 제거돼도 DOM 리스너가 남으면 메모리 누수와 중복 실행이 발생할 수 있습니다.
TypeScript와 API 연동
TypeScript는 메서드의 this 매개변수를 표시하고 잘못된 호출을 일부 찾아낼 수 있지만 런타임 바인딩을 바꾸지는 않습니다. JSON으로 클래스 인스턴스를 보내면 메서드와 프로토타입 정보는 사라지고 데이터만 남습니다. 다른 언어에서 받은 JSON을 클래스 타입으로 단언한다고 실제 인스턴스가 되지 않으므로 생성자나 팩터리로 검증하고 변환해야 합니다.
테스트 체크리스트
- 객체 메서드 직접 호출과 분리 호출을 비교합니다.
- bind한 콜백의 등록과 해제에 같은 참조를 사용합니다.
- 화살표 콜백이 바깥 this를 유지하는지 확인합니다.
- 프로토타입 메서드와 인스턴스 필드의 차이를 봅니다.
- 상속 생성자에서 super 호출 순서를 테스트합니다.
- JSON 복원 후 메서드 존재 여부를 검증합니다.
- 화면 제거 뒤 이벤트가 중복 실행되지 않는지 확인합니다.
this가 복잡하게 느껴질수록 모든 상태를 객체에 숨기기보다 순수 함수와 명시적인 매개변수를 활용할 수 있습니다. 클래스는 상태와 행동의 결합이 자연스러운 도메인에 사용하고, 단순 변환은 함수로 유지하면 컨텍스트 의존성이 줄어듭니다. 호출부를 읽는 습관이 this 문제를 해결하는 가장 빠른 출발점입니다.
한 줄 요약: 자바스크립트 this는 선언 위치보다 호출 방식이 핵심이며 콜백 바인딩, 화살표 함수, 프로토타입의 차이를 명시적으로 선택해야 합니다.