2025-09-23 20:32

  • 자바스크립트 this는 함수를 호출하는 객체를 가리키는 동적인 참조로, 코드의 재사용성을 높이고 객체 지향 프로그래밍을 지원하기 위해 만들어졌다.

  • this는 호출 방식에 따라 결정되며, 일반 함수 호출, 메서드 호출, 생성자 함수 호출, 그리고 화살표 함수 등 다양한 상황에서 다른 값을 가진다.

  • call(), apply(), bind() 메서드를 사용하면 this의 값을 명시적으로 지정할 수 있어, 복잡한 상황에서도 this를 제어하며 유연한 코드를 작성할 수 있다.

자바스크립트 this 완벽 정복 핸드북 개발자를 위한 최종 안내서

자바스크립트 개발자라면 누구나 한 번쯤 this라는 키워드 앞에서 좌절감을 맛본 경험이 있을 것이다. 어떤 때는 객체를 가리키다가도, 다른 때는 엉뚱한 값을 참조하며 개발자를 혼란에 빠뜨린다. 하지만 this는 자바스크립트의 핵심 개념 중 하나이며, 이를 제대로 이해하지 못하면 코드의 동작 방식을 정확히 예측하고 제어하기 어렵다.

이 핸드북은 자바스크립트의 this가 왜 만들어졌는지 그 탄생 배경부터 시작하여, 다양한 상황에서 this가 어떻게 결정되는지 그 구조를 파헤치고, 실제 코드에서 어떻게 활용되는지 구체적인 사용법과 심화 내용까지 모든 것을 담았다. 이 글을 끝까지 읽는다면, 더 이상 this 때문에 디버깅에 시간을 낭비하는 일은 없을 것이다.

1. this는 왜 만들어졌을까 탄생 배경과 철학

자바스크립트는 처음부터 객체 지향 프로그래밍(OOP) 언어로 설계되었다. 객체 지향 프로그래밍의 핵심은 관련된 데이터(속성)와 행동(메서드)을 하나의 단위인 **객체(Object)**로 묶는 것이다.

예를 들어, ‘사용자’라는 객체를 생각해보자. 이 객체에는 ‘이름’이나 ‘이메일’과 같은 데이터와 ‘로그인하다’나 ‘글을 쓰다’와 같은 행동이 포함될 수 있다.

JavaScript

let user = {
  name: "Alice",
  email: "alice@example.com",
  login: function() {
    console.log(this.name + "님이 로그인했습니다.");
  }
};

user.login(); // "Alice님이 로그인했습니다."

여기서 login이라는 메서드는 user 객체의 name 속성에 접근해야 한다. 이때 this는 바로 login 메서드를 호출한 객체, 즉 user를 가리키는 참조 역할을 한다. 만약 this가 없다면, 메서드 내부에서 자신이 속한 객체의 속성에 접근하기 위해 매번 객체의 이름을 명시해야 하는 번거로움이 발생할 것이다.

JavaScript

// this가 없다면?
let user = {
  name: "Alice",
  login: function() {
    console.log(user.name + "님이 로그인했습니다."); // 'user'를 직접 참조
  }
};

이는 코드의 재사용성을 심각하게 저해한다. 다른 이름의 객체에서 이 메서드를 재사용하려면 메서드 내부의 user라는 이름도 모두 바꿔야 하기 때문이다.

this는 바로 이러한 문제를 해결하기 위해 탄생했다. this는 함수(메서드)가 호출되는 시점에 자신이 속한 객체를 동적으로 가리킴으로써, 코드를 유연하고 재사용 가능하게 만든다. 즉, this는 **실행 컨텍스트(Execution Context)**에 따라 달라지는 동적인 식별자 참조인 셈이다.

2. this의 결정 원리 상황별 구조 분석

this의 값은 함수가 어떻게 호출되었느냐에 따라 결정된다. 이는 this를 이해하는 가장 중요한 핵심 원칙이다. this가 결정되는 주요 상황을 5가지로 나누어 그 구조를 자세히 분석해 보자.

2.1. 전역 컨텍스트 (Global Context)

가장 바깥 범위, 즉 어떤 함수 내부도 아닌 곳에서 this를 호출하면 전역 객체를 가리킨다. 브라우저 환경에서는 window 객체가, Node.js 환경에서는 global 객체가 전역 객체에 해당한다.

JavaScript

console.log(this); // 브라우저: Window {...}, Node.js: Object [global] {...}

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

showThis(); // 브라우저: Window {...} (엄격 모드에서는 undefined)

주의할 점: 일반 함수 내부에서 호출된 this도 기본적으로는 전역 객체를 가리킨다. 하지만 **엄격 모드('use strict')**에서는 undefined를 가리키므로, 의도치 않은 전역 객체 오염을 방지하기 위해 엄격 모드를 사용하는 것이 좋다.

2.2. 메서드 호출 (Method Invocation)

함수가 객체의 속성(메서드)으로서 호출될 때, this메서드를 호출한 객체를 가리킨다. 이는 this의 가장 일반적이고 직관적인 사용 방식이다.

JavaScript

let user = {
  name: "John",
  sayHello: function() {
    console.log("Hello, " + this.name);
  }
};

user.sayHello(); // "Hello, John"
// sayHello를 호출한 객체는 user이므로, this는 user를 가리킨다.

메서드가 여러 객체에 중첩되어 있는 경우에도 원리는 동일하다. this마침표(.) 바로 앞에 있는 객체를 가리킨다.

JavaScript

let school = {
  name: "IT High School",
  student: {
    name: "Tom",
    introduce: function() {
      console.log("I am " + this.name + " from " + school.name);
    }
  }
};

school.student.introduce(); // "I am Tom from IT High School"
// introduce를 호출한 객체는 student이므로, this는 student를 가리킨다.

2.3. 생성자 함수 호출 (Constructor Invocation)

new 키워드를 사용하여 함수를 호출하면, 해당 함수는 생성자 함수로 동작한다. 이때 this새롭게 생성되는 인스턴스 객체를 가리킨다.

생성자 함수는 다음과 같은 과정을 거친다.

  1. 빈 객체가 생성된다. (this는 이 객체를 가리킨다.)

  2. this에 새로운 속성들이 추가된다.

  3. 별도의 return문이 없으면 this가 암묵적으로 반환된다.

JavaScript

function Person(name, age) {
  // 1. 빈 객체가 생성되고 this에 바인딩된다.
  this.name = name; // 2. this에 속성을 추가한다.
  this.age = age;
}

// 3. new 키워드로 생성자 함수를 호출하면 this가 반환된다.
let person1 = new Person("Alice", 25);
let person2 = new Person("Bob", 30);

console.log(person1.name); // "Alice"
console.log(person2.name); // "Bob"

2.4. apply, call, bind를 통한 명시적 바인딩

자바스크립트는 this를 명시적으로 지정할 수 있는 세 가지 메서드 apply(), call(), bind()를 제공한다. 이를 통해 this의 동적인 특성을 제어할 수 있다.

메서드설명사용법 예시
call(thisArg, arg1, arg2, ...)this 값을 지정하고, 인자를 개별적으로 전달하여 함수를 즉시 호출한다.sayHello.call(user1, "Good morning");
apply(thisArg, [arg1, arg2, ...])this 값을 지정하고, 인자를 배열 형태로 전달하여 함수를 즉시 호출한다.sayHello.apply(user2, ["Good afternoon"]);
bind(thisArg)this 값이 영구적으로 고정된 새로운 함수를 반환한다. 함수를 즉시 호출하지 않는다.const boundSayHello = sayHello.bind(user3); boundSayHello("Good evening");

callapply는 인자를 전달하는 방식에만 차이가 있을 뿐, 기능적으로는 동일하다. bind는 특히 콜백 함수나 이벤트 핸들러에서 this를 특정 객체에 고정시키고 싶을 때 유용하게 사용된다.

JavaScript

function introduce(greeting) {
  console.log(greeting + ", my name is " + this.name);
}

const developer = { name: "Chris" };
const designer = { name: "Diana" };

// call 사용
introduce.call(developer, "Hi"); // "Hi, my name is Chris"

// apply 사용
introduce.apply(designer, ["Hello"]); // "Hello, my name is Diana"

// bind 사용
const introduceChris = introduce.bind(developer);
introduceChris("Hey"); // "Hey, my name is Chris"

2.5. 화살표 함수 (Arrow Functions)

ES6에서 도입된 화살표 함수는 this를 다루는 방식이 기존 함수와 근본적으로 다르다. 화살표 함수는 자신만의 this를 가지지 않는다. 대신, 자신을 감싸고 있는 상위 스코프(Lexical Scope)의 this를 그대로 물려받는다.

이러한 특징 때문에 화살표 함수는 콜백 함수 내부에서 this가 달라지는 문제를 해결하는 데 매우 유용하다.

JavaScript

// 기존 함수 사용 시 문제점
function Timer() {
  this.seconds = 0;
  setInterval(function() {
    this.seconds++; // 여기서 this는 window (또는 global)를 가리킴
    console.log(this.seconds); // NaN
  }, 1000);
}

// let timer = new Timer(); // 의도대로 동작하지 않음

// 해결책 1: bind 사용
function TimerBind() {
  this.seconds = 0;
  setInterval(function() {
    this.seconds++;
    console.log(this.seconds);
  }.bind(this), 1000); // this를 명시적으로 바인딩
}

// let timerBind = new TimerBind();

// 해결책 2: 화살표 함수 사용 (가장 깔끔한 해결책)
function TimerArrow() {
  this.seconds = 0;
  setInterval(() => {
    this.seconds++; // 화살표 함수는 상위 스코프(TimerArrow)의 this를 참조
    console.log(this.seconds);
  }, 1000);
}

let timerArrow = new TimerArrow();

3. this 실전 활용법 및 주의사항

this의 동작 원리를 이해했다면, 이제 실제 코드에서 어떻게 활용하고 어떤 점을 주의해야 하는지 알아보자.

3.1. 이벤트 핸들러에서의 this

DOM 이벤트 핸들러에서 this는 **이벤트가 발생한 요소(Element)**를 가리킨다.

HTML

<button id="myButton">Click me</button>

JavaScript

const button = document.getElementById('myButton');

button.addEventListener('click', function() {
  console.log(this); // <button id="myButton">...</button>
  this.textContent = 'Clicked!';
});

단, 이벤트 핸들러로 화살표 함수를 사용하면 this는 상위 스코프(대개 windowundefined)를 가리키게 되므로 주의해야 한다.

JavaScript

button.addEventListener('click', () => {
  console.log(this); // window 객체를 가리킴
  // this.textContent = 'Clicked!'; // TypeError 발생
});

3.2. 콜백 함수와 this 문제

콜백 함수는 다른 함수의 인자로 전달되는 함수다. 이때 콜백 함수 내부의 this는 의도치 않게 전역 객체를 가리키는 경우가 많아 문제가 발생하곤 한다.

JavaScript

let user = {
  name: "Alice",
  friends: ["Bob", "Charlie"],
  printFriends: function() {
    this.friends.forEach(function(friend) {
      // forEach의 콜백 함수 내부의 this는 window를 가리킨다.
      console.log(this.name + " is friends with " + friend);
    });
  }
};

// user.printFriends(); // TypeError: Cannot read property 'name' of undefined

이 문제를 해결하는 방법은 앞서 살펴본 bind나 화살표 함수를 사용하는 것이다.

JavaScript

// 해결책 1: bind 사용
this.friends.forEach(function(friend) {
  console.log(this.name + " is friends with " + friend);
}.bind(this));

// 해결책 2: 화살표 함수 사용
this.friends.forEach(friend => {
  console.log(this.name + " is friends with " + friend);
});

4. 심화 탐구 this 우선순위

만약 여러 this 규칙이 동시에 적용될 수 있는 상황이라면 어떤 규칙이 우선할까? this의 바인딩 우선순위는 다음과 같다.

  1. new 바인딩: new 키워드로 생성자 함수를 호출할 때가 가장 높은 우선순위를 가진다.

  2. 명시적 바인딩: call(), apply(), bind()를 사용한 경우가 그 다음이다.

  3. 메서드 호출 바인딩: 객체의 메서드로 호출될 때가 세 번째다.

  4. 기본 바인딩: 위 경우에 해당하지 않으면 전역 객체(엄격 모드에서는 undefined)에 바인딩된다.

화살표 함수는 이 우선순위 규칙을 따르지 않고 항상 상위 스코프의 this를 따른다는 점을 기억하자.

결론

자바스크립트의 this는 처음에는 복잡하고 예측 불가능해 보일 수 있다. 하지만 그 핵심 원리는 **“함수가 어떻게 호출되었는가?”**라는 하나의 질문으로 귀결된다. this는 함수가 선언될 때가 아니라, 호출될 때 그 값이 결정되는 동적인 존재다.

이 핸드북에서 다룬 다섯 가지 상황별 결정 원리(전역, 메서드, 생성자, 명시적 바인딩, 화살표 함수)와 우선순위를 명확히 이해한다면, 더 이상 this는 혼란의 대상이 아닌 코드를 더욱 유연하고 강력하게 만들어주는 훌륭한 도구가 될 것이다. this를 정복하고 한 단계 더 높은 수준의 자바스크립트 개발자로 거듭나기를 바란다.