같은 함수인데 obj.greet()로 호출하면 thisobj이고, const fn = obj.greet; fn()으로 호출하면 thisundefined가 됩니다. 왜 호출 방식에 따라 this가 달라지는 걸까요?

자바스크립트의 this는 함수가 선언된 위치가 아니라 호출된 방식 에 의해 결정됩니다. 이 특성 때문에 콜백으로 메서드를 전달할 때 this가 풀리는 문제가 발생합니다.

this는 호출 시점에 결정된다

자바스크립트의 this함수가 선언된 위치가 아니라, 호출된 방식 에 의해 결정됩니다. 이것이 다른 언어와 가장 큰 차이점입니다.

네 가지 바인딩 규칙이 있고, 우선순위도 정해져 있습니다.

규칙 1: 기본 바인딩 (Default Binding)

아무 조건에도 해당하지 않을 때 적용되는 기본 규칙입니다.

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

showThis(); // 비엄격 모드: window (전역 객체) / 엄격 모드: undefined

엄격 모드의 영향

JS
'use strict';

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

strictShow(); // undefined

비엄격 모드에서 this가 전역 객체로 바인딩되는 것은 언어 설계상의 실수로 여겨집니다. 엄격 모드에서는 이 동작이 수정되어 undefined가 됩니다.

ES 모듈(type="module")은 기본적으로 엄격 모드이므로, 현대 프로젝트에서는 대부분 undefined입니다.

규칙 2: 암묵적 바인딩 (Implicit Binding)

함수가 객체의 메서드로 호출 될 때, this는 그 객체를 가리킵니다.

JS
const user = {
  name: 'Alice',
  greet() {
    console.log(`안녕하세요, ${this.name}입니다.`);
  }
};

user.greet(); // '안녕하세요, Alice입니다.'

**핵심 **: 호출 시점에 . 앞에 있는 객체가 this입니다.

암묵적 바인딩의 함정 — 참조 손실

JS
const user = {
  name: 'Alice',
  greet() {
    console.log(this.name);
  }
};

// 메서드를 변수에 할당하면 참조가 끊어짐
const fn = user.greet;
fn(); // undefined (비엄격 모드에서는 window.name)

fn()은 단순 함수 호출이므로 기본 바인딩이 적용됩니다. 이 패턴은 ** 콜백으로 메서드를 전달할 때** 자주 발생합니다.

JS
const user = {
  name: 'Alice',
  greet() {
    console.log(this.name);
  }
};

// 콜백으로 전달하면 암묵적 바인딩이 사라짐
setTimeout(user.greet, 1000); // undefined

중첩 객체

중첩 객체에서는 ** 직전 객체 **가 this입니다.

JS
const company = {
  name: '회사',
  team: {
    name: '개발팀',
    getName() {
      return this.name;
    }
  }
};

console.log(company.team.getName()); // '개발팀' — team이 this

규칙 3: 명시적 바인딩 (Explicit Binding)

call, apply, bind를 사용하여 this를 직접 지정합니다.

call

JS
function greet(greeting) {
  console.log(`${greeting}, ${this.name}`);
}

const user = { name: 'Alice' };

greet.call(user, '안녕하세요'); // '안녕하세요, Alice'

인자를 ** 개별적으로** 전달합니다.

apply

JS
greet.apply(user, ['안녕하세요']); // '안녕하세요, Alice'

인자를 ** 배열로** 전달합니다. callapply의 차이는 인자 전달 방식뿐입니다.

JS
// 외우는 팁: Apply는 Array, Call은 Comma

bind

JS
const boundGreet = greet.bind(user);
boundGreet('안녕하세요'); // '안녕하세요, Alice'

bind는 ** 새로운 함수를 반환 **합니다. call/apply처럼 즉시 실행하지 않고, this가 고정된 함수를 만듭니다.

JS
// 콜백에서의 this 문제 해결
const user = {
  name: 'Alice',
  greet() {
    console.log(this.name);
  }
};

// bind로 this 고정
setTimeout(user.greet.bind(user), 1000); // 'Alice'

하드 바인딩

bind로 고정된 this는 다시 변경할 수 없습니다.

JS
function show() {
  console.log(this.name);
}

const bound = show.bind({ name: 'Alice' });
bound.call({ name: 'Bob' }); // 'Alice' — call로도 변경 불가

규칙 4: new 바인딩

new 키워드로 함수를 호출하면 새로운 객체가 생성되고, this가 그 객체에 바인딩됩니다.

JS
function User(name) {
  // this = {} (새로 생성된 빈 객체)
  this.name = name;
  // return this (암묵적 반환)
}

const user = new User('Alice');
console.log(user.name); // 'Alice'

new가 호출되면 내부적으로 네 가지 일이 벌어집니다.

  1. 빈 객체 생성
  2. 새 객체의 [[Prototype]]을 생성자의 prototype에 연결
  3. this를 새 객체에 바인딩하고 함수 실행
  4. 함수가 객체를 반환하지 않으면 새 객체를 자동 반환

바인딩 우선순위

네 가지 규칙이 충돌할 때 ** 우선순위 **가 있습니다.

PLAINTEXT
new 바인딩 > 명시적 바인딩(bind) > 암묵적 바인딩 > 기본 바인딩

우선순위 확인

JS
function foo() {
  console.log(this.a);
}

const obj1 = { a: 1, foo };
const obj2 = { a: 2, foo };

// 암묵적 vs 명시적 → 명시적 승리
obj1.foo.call(obj2); // 2

// bind vs 암묵적 → bind 승리
const boundFoo = foo.bind(obj1);
obj2.foo = boundFoo;
obj2.foo(); // 1

// new vs bind → new 승리
const BoundFoo = foo.bind(obj1);
const instance = new BoundFoo();
console.log(instance.a); // undefined — 새 객체가 this

화살표 함수의 렉시컬 this

화살표 함수(=>)는 위 네 가지 규칙을 ** 전부 무시 **합니다. 자체 this가 없고, ** 선언된 시점의 상위 스코프의 this를 그대로 사용 **합니다.

JS
const user = {
  name: 'Alice',
  greet() {
    // 일반 함수: this는 user
    const inner = () => {
      // 화살표 함수: 상위 스코프(greet)의 this를 상속 → user
      console.log(this.name);
    };
    inner();
  }
};

user.greet(); // 'Alice'

화살표 함수와 메서드 — 주의할 점

JS
const user = {
  name: 'Alice',
  // 화살표 함수로 메서드 정의 — 안티패턴!
  greet: () => {
    console.log(this.name); // this는 상위 스코프(전역)
  }
};

user.greet(); // undefined

객체 리터럴은 스코프를 만들지 않으므로, 화살표 함수의 상위 스코프는 전역입니다. ** 메서드 정의에는 일반 함수(축약 메서드)를 사용 **해야 합니다.

화살표 함수에 call/bind는 무의미

JS
const arrow = () => console.log(this);

arrow.call({ name: 'Alice' }); // 전역 객체 — call 무시됨
const bound = arrow.bind({ name: 'Bob' });
bound(); // 전역 객체 — bind 무시됨

화살표 함수는 this를 바인딩하는 메커니즘 자체가 없으므로, call/apply/bindthis에 영향을 주지 않습니다.

클래스에서의 this

JS
class Timer {
  constructor() {
    this.seconds = 0;
  }

  start() {
    // 화살표 함수로 this를 렉시컬하게 유지
    setInterval(() => {
      this.seconds++; // Timer 인스턴스를 가리킴
      console.log(this.seconds);
    }, 1000);
  }
}

const timer = new Timer();
timer.start();

만약 setInterval에 일반 함수를 넣으면 this는 전역 객체가 됩니다. 이것이 이벤트 핸들러나 콜백에서 화살표 함수를 많이 쓰는 이유입니다.

클래스 필드와 화살표 함수

JS
class Button {
  // 클래스 필드에 화살표 함수 할당 — 인스턴스마다 새 함수 생성
  handleClick = () => {
    console.log(this); // 항상 Button 인스턴스
  };
}

const btn = new Button();
const fn = btn.handleClick;
fn(); // Button 인스턴스 — 참조 손실 없음

React에서 이벤트 핸들러를 이런 식으로 작성하는 이유가 바로 this 바인딩 문제를 피하기 위해서입니다.

this 판별 순서

코드에서 this가 무엇인지 판별하는 순서입니다.

  1. ** 화살표 함수인가?** → 상위 스코프의 this
  2. new로 호출했는가? → 새로 생성된 객체
  3. call/apply/bind를 사용했는가? → 지정된 객체
  4. ** 객체의 메서드로 호출했는가?** → 그 객체
  5. ** 그 외** → 전역 객체(비엄격) 또는 undefined(엄격)
JS
const obj = {
  value: 42,
  getValue() { return this.value; },
  getValueArrow: () => this.value
};

console.log(obj.getValue());      // 42 — 암묵적 바인딩
console.log(obj.getValueArrow()); // undefined — 전역의 this

const get = obj.getValue;
console.log(get());               // undefined — 기본 바인딩

console.log(obj.getValue.call({ value: 100 })); // 100 — 명시적 바인딩

주의할 점

메서드를 콜백으로 전달할 때 this가 풀린다

setTimeout(user.greet, 1000)처럼 메서드를 콜백으로 넘기면, 호출 시점에 . 앞에 객체가 없으므로 기본 바인딩이 적용됩니다. bind로 this를 고정하거나, 화살표 함수로 감싸야 합니다.

객체 리터럴에서 화살표 함수로 메서드를 정의하면 안 된다

객체 리터럴은 스코프를 생성하지 않습니다. 화살표 함수의 상위 스코프가 전역이 되어, this가 전역 객체(또는 undefined)를 가리킵니다. 메서드 정의에는 반드시 축약 메서드 문법을 사용해야 합니다.

bind된 함수의 this는 다시 바꿀 수 없다

bind로 고정한 this는 call이나 apply로도 변경되지 않습니다(하드 바인딩). 단, new 연산자는 bind보다 우선순위가 높아서 새 객체를 this로 만듭니다.

정리

항목설명
기본 바인딩전역 객체(비엄격) 또는 undefined(엄격)
암묵적 바인딩. 앞의 객체가 this
명시적 바인딩call(개별 인자), apply(배열), bind(새 함수 반환)
new 바인딩새로 생성된 객체가 this
화살표 함수자체 this 없음, 상위 스코프에서 렉시컬하게 상속
우선순위new > bind > 암묵적 > 기본
댓글 로딩 중...