1. 개요

this가 헷갈리는 이유는 단 하나다. 다른 언어처럼 클래스 정의 시점에 정해지는 게 아니라 함수를 호출하는 방식에 따라 매번 새로 정해진다. 같은 함수라도 어떻게 부르느냐에 따라 this가 달라진다. 네 가지 호출 방식과 화살표 함수의 예외, 그리고 call/apply/bind로 강제 고정하는 법을 본다.


2. 핵심 내용

2-1. 메서드로 호출하면 그 객체

점(.) 앞에 있는 객체가 this다.

const user = {
  name: "준호",
  greet() { return `안녕, ${this.name}`; },
};
user.greet(); // "안녕, 준호"

2-2. 함수로 그냥 호출하면 undefined 또는 전역 객체

객체 없이 함수만 호출하면 this를 정해줄 대상이 없다. strict mode(모듈, class 내부는 기본 strict)에서는 undefined, 아니면 전역 객체(브라우저는 window)다. 메서드를 꺼내서 변수에 담아 넘기면 바로 이 함정에 빠진다.

const greet = user.greet;
greet(); // TypeError: Cannot read properties of undefined (strict mode)

콜백으로 메서드를 넘길 때(button.onclick = user.greet) 흔히 나는 버그다. this가 user가 아니라 호출 시점의 대상이 되거나 undefined가 된다.

2-3. 생성자로 호출하면 새 객체

new로 호출하면 엔진이 새 객체를 만들어 this에 연결하고, 함수가 끝나면 그 객체를 반환한다.

function User(name) { this.name = name; }
const u = new User("준호");
u.name; // "준호"

2-4. 화살표 함수는 this 가 없다

화살표 함수는 자기만의 this를 만들지 않는다. 정의된 위치(렉시컬 스코프)의 this를 그대로 물려받는다. 그래서 콜백 안에서 바깥 this를 쓰고 싶을 때 화살표 함수를 쓴다.

const timer = {
  seconds: 0,
  start() {
    setInterval(() => { this.seconds++; }, 1000); // this === timer
  },
};

여기서 일반 함수(function () {...})를 썼다면 this는 timer가 아니라 호출 주체(전역 또는 undefined)가 됐을 것이다.

2-5. call, apply, bind 로 고정하기

this를 명시적으로 지정하고 싶을 때 쓴다.

function greet() { return `안녕, ${this.name}`; }
const user = { name: "준호" };
greet.call(user);               // 즉시 호출, 인자는 하나씩
greet.apply(user, []);          // 즉시 호출, 인자는 배열로
const bound = greet.bind(user); // 새 함수를 만들어 this 를 영구 고정
bound();

bind는 콜백으로 메서드를 넘겨야 하는데 this가 깨지면 안 될 때 쓴다. class의 이벤트 핸들러 메서드를 생성자에서 this.handleClick = this.handleClick.bind(this)로 묶는 패턴이 여기서 나온다.


3. 마무리

요약

  • this는 정의 시점이 아니라 호출 방식이 정한다
  • 메서드 호출은 그 객체, 함수 호출은 undefined/전역, new는 새 객체
  • 메서드를 변수에 담아 콜백으로 넘기면 this가 끊어진다
  • 화살표 함수는 자기 this가 없고 정의 위치의 this를 그대로 쓴다
  • call/apply는 즉시 호출하며 지정, bind는 영구 고정한 새 함수를 만든다

다음은 프로토타입 체인과 class 이다.