JavaScript의 비밀스러운 삶: ‘this’ 이해하기

발행: (2025년 12월 8일 오후 01:41 GMT+9)
7 min read
원문: Dev.to

Source: Dev.to

Chapter 2: 컨텍스트가 전부다

Timothy는 따뜻한 차 한 잔을 들고 도서관에 도착했다. 그는 아침 내내 간단한 JavaScript 애플리케이션을 디버깅하느라 머리가 복잡해졌다.

Timothy: “Margaret, 도움이 필요해. 똑같이 보이는 코드를 썼는데 this가 잘못된 대상을 가리켜. 때로는 기대한 객체, 때로는 undefined, 때로는 전역 window 객체가 돼. 미치겠어.”

Margaret는 그의 코드를 바라보았다—버튼에 연결된 간단한 이벤트 핸들러였다.

const user = {
  name: "Alice",
  greet: function() {
    console.log("Hello, " + this.name);
  }
};

user.greet(); // "Hello, Alice" - works!

const greetFunction = user.greet;
greetFunction(); // "Hello, undefined" - WHAT?!

그녀는 알아채는 듯 미소 지었다.

Margaret: “아, JavaScript 혼란의 가장 유명한 원천이구나. this가 네가 생각하는 것이 아니라는 진실을 발견했어.”

Timothy: “하지만 같은 함수잖아! 왜 다르게 동작해?”

Margaret: “왜냐하면 JavaScript에서 this함수가 정의된 위치에 의해 결정되지 않아. 함수가 호출되는 방식에 의해 결정되지. 파이썬과는 완전히 다른 사고 모델이야.”

Timothy는 신음했다.

Timothy: “이미 파이썬과 비교하고 있네?”

Margaret: “그 차이를 이해해야 해. 파이썬에서는 self가 명시적이야. 모든 메서드 시그니처에 적어야 하고, 모호함이 없어. JavaScript에서는 this가 암시적이야. 정의 시점이 아니라 호출 시점에 결정돼. 여기서 혼란이 생기는 거지.”

this의 네 가지 규칙

Margaret는 낡은 노트를 꺼내 “JavaScript 미스터리”라고 적힌 섹션을 펼쳤다.

규칙 1: 메서드 호출

const user = {
  name: "Alice",
  greet: function() {
    console.log(this); // The object (user)
  }
};

user.greet(); // this = user

객체의 메서드로 함수를 호출할 때—점 표기법을 사용하면—this는 그 객체 자체를 가리킨다. 직관적이며 대부분이 기대하는 동작이다.

규칙 2: 함수 호출

function greet() {
  console.log(this);
}

greet(); // this = undefined (in strict mode) or window (in non-strict)

함수를 직접 호출하면(객체의 메서드가 아니라) this는 strict 모드 여부에 따라 달라진다. strict 모드(현대 JavaScript)에서는 thisundefined이고, 비‑strict 모드에서는 전역 객체(window 혹은 Node.js의 global)가 된다. 어느 경우든 보통 원하는 값은 아니다.

Timothy: “왜 그렇게 코딩하겠어?”

Margaret: “아무도 의도적으로 그렇게 하지 않아. 하지만 객체에서 메서드를 추출했을 때 무슨 일이 일어나는지 보자.”

const user = {
  name: "Alice",
  greet: function() {
    console.log("Hello, " + this.name);
  }
};

user.greet(); // "Hello, Alice" – Rule 1 (method call)

const greetFunction = user.greet; // Extract the function
greetFunction(); // "Hello, undefined" – Rule 2 (function call)

메서드를 추출하면 일반 함수 호출이 되므로 thisundefined가 된다. 함수는 자신이 어디서 왔는지 기억하지 못한다.

규칙 3: 생성자 호출

function User(name) {
  this.name = name;
  console.log(this); // A brand‑new object
}

const alice = new User("Alice");
console.log(alice.name); // "Alice"

new 키워드를 사용하면 새로운 객체가 생성되고 this가 그 객체에 바인딩된다. 그 뒤에 생성자는 해당 객체에 속성을 채워 넣는다.

규칙 4: call, apply, bind 로 명시적 바인딩

function greet(greeting) {
  console.log(greeting + ", " + this.name);
}

const user = { name: "Alice" };

greet.call(user, "Hello");   // "Hello, Alice"
greet.apply(user, ["Hello"]); // "Hello, Alice"

const boundGreet = greet.bind(user);
boundGreet("Hello"); // "Hello, Alice"

callapply는 단일 호출에 대해 this를 명시적으로 지정하고, bindthis가 영구히 바인딩된 새 함수를 반환한다.

this 판단을 위한 의사결정 트리

함수가 어떻게 호출되었는가?

├─ 'new'와 함께 호출했는가?
│  └─ 규칙 3: 생성자 호출
│     → this = 새로 만든 객체

├─ .call(), .apply(), .bind()와 함께 호출했는가?
│  └─ 규칙 4: 명시적 바인딩
│     → this = 지정한 객체

├─ object.method() 형태로 호출했는가?
│  └─ 규칙 1: 메서드 호출
│     → this = 점 앞의 객체

└─ 그냥 function() 형태로 호출했는가?
   └─ 규칙 2: 함수 호출
      → this = undefined (strict) 혹은 전역 객체 (non‑strict)

this 관련 버그를 디버깅할 때는 이 트리를 따라가 어떤 규칙이 적용되는지 확인한다.

문제: 컨텍스트 상실

Timothy는 원래 문제가 메서드를 추출하면서 규칙 2가 적용된 것이란 것을 깨달았다.

Margaret는 이벤트 리스너와 함께 실제 예시를 보여주었다:

const button = document.querySelector('button');

const user = {
  name: "Alice",
  handleClick: function() {
    console.log(this.name); // What is this?
  }
};

button.addEventListener('click', user.handleClick);
// When clicked: console.log undefined
// addEventListener calls the function with `this` set to the button element,

addEventListener는 핸들러를 호출할 때 this를 버튼 요소에 바인딩하므로 this.nameundefined가 된다.

해결 방법

해결 1: 화살표 함수 사용

const user = {
  name: "Alice",
  setupButton: function() {
    const button = document.querySelector('button');
    button.addEventListener('click', () => {
      console.log(this.name); // this = user (captured from setupButton's scope)
    });
  }
};

user.setupButton();

화살표 함수는 주변 렉시컬 스코프에서 this를 상속받아 user 컨텍스트를 유지한다.

해결 2: bind 사용

button.addEventListener('click', user.handleClick.bind(user));
// When clicked: console.log "Alice"
Back to Blog

관련 글

더 보기 »

함수와 화살표 함수

함수란 무엇인가요? 간단히 말하면, 함수는 JavaScript의 주요 구성 요소 중 하나입니다. 함수는 코드를 작은…

JavaScript 제너레이터: 코드의 'Pause' 버튼

소개 표준 JavaScript 함수에 대해 이해하고 있다면, 실행 완료 규칙(run‑to‑completion rule)을 알고 있을 것입니다: 일반 함수가 실행을 시작하면, 아무것도 그것을 멈출 수 없습니다 u...