전체 목록
JavaScriptMedium#07

this 키워드는 어떻게 결정되며, 화살표 함수에서는 어떻게 다른가요?

#JS#this#화살표함수#함수
힌트

일반 함수는 호출 방식에 따라, 화살표 함수는 선언 시 렉시컬 환경에 따라 결정됩니다.

정답 및 해설

this 키워드는 어떻게 결정되며, 화살표 함수에서는 어떻게 다른가요?

this는 JavaScript에서 가장 혼란스러운 개념 중 하나입니다. 다른 언어와 달리, JavaScript의 this함수가 어떻게 호출되었는지에 따라 동적으로 결정됩니다. 단, 화살표 함수는 이 규칙에서 예외로 선언 시점의 상위 스코프 this를 사용합니다.

일반 함수에서 this의 결정 규칙

1. 전역 컨텍스트 / 단독 호출

// 브라우저 환경 (strict mode 아닌 경우)
function showThis() {
  console.log(this); // window (전역 객체)
}
showThis();

// strict mode
function showThisStrict() {
  'use strict';
  console.log(this); // undefined
}
showThisStrict();

2. 메서드 호출 — 점(.) 앞의 객체

this는 메서드를 호출한 객체를 가리킵니다.

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

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

// 메서드를 변수에 할당하면 this 컨텍스트를 잃음
const greetFn = user.greet;
greetFn(); // '안녕하세요, undefined입니다.' (this = 전역 or undefined)

3. 생성자 호출 (new)

new로 호출하면 this는 새로 생성된 인스턴스를 가리킵니다.

function Person(name) {
  this.name = name; // this = 새로 생성된 객체
  this.greet = function () {
    return `Hi, ${this.name}`;
  };
}

const alice = new Person('Alice');
console.log(alice.greet()); // 'Hi, Alice'

4. 명시적 바인딩 — call, apply, bind

function introduce(greeting, punctuation) {
  return `${greeting}, 저는 ${this.name}입니다${punctuation}`;
}

const person = { name: 'Bob' };

// call — 인수를 쉼표로 전달
console.log(introduce.call(person, '안녕하세요', '!'));
// '안녕하세요, 저는 Bob입니다!'

// apply — 인수를 배열로 전달
console.log(introduce.apply(person, ['반갑습니다', '.']));
// '반갑습니다, 저는 Bob입니다.'

// bind — 새 함수를 반환 (즉시 실행하지 않음)
const boundIntroduce = introduce.bind(person, '처음 뵙겠습니다');
console.log(boundIntroduce('~'));
// '처음 뵙겠습니다, 저는 Bob입니다~'

this 결정 우선순위

new 바인딩 > 명시적 바인딩(call/apply/bind) > 메서드 바인딩 > 기본 바인딩(전역/undefined)

화살표 함수의 this — 렉시컬 this

화살표 함수는 자신만의 this를 가지지 않습니다. 선언된 시점의 상위(외부) 스코프의 this를 그대로 사용합니다. 이를 **렉시컬 this(Lexical this)**라고 합니다.

const timer = {
  count: 0,

  // 일반 함수 — this가 undefined 또는 전역 객체 (콜백이므로)
  startBad() {
    setInterval(function () {
      this.count++; // this가 timer가 아님!
      console.log(this.count); // NaN
    }, 1000);
  },

  // 화살표 함수 — 상위 스코프(startGood 메서드)의 this = timer
  startGood() {
    setInterval(() => {
      this.count++; // this = timer
      console.log(this.count); // 1, 2, 3, ...
    }, 1000);
  },
};

클래스에서의 활용

class Button {
  constructor(label) {
    this.label = label;
    this.clickCount = 0;
  }

  // 일반 메서드 — 이벤트 핸들러로 사용 시 this가 달라질 수 있음
  handleClickBad() {
    this.clickCount++;
    console.log(`${this.label} 클릭 횟수: ${this.clickCount}`);
  }

  // 화살표 함수 필드 — this가 인스턴스에 고정됨
  handleClickGood = () => {
    this.clickCount++;
    console.log(`${this.label} 클릭 횟수: ${this.clickCount}`);
  };

  mount(element) {
    // 일반 메서드를 이벤트 핸들러로 등록하면 this가 element가 됨
    element.addEventListener('click', this.handleClickBad); // 버그!

    // 화살표 함수 필드는 안전
    element.addEventListener('click', this.handleClickGood); // 정상
  }
}

call, apply, bind가 화살표 함수에 통하지 않는 이유

화살표 함수는 렉시컬 this를 사용하므로 call, apply, bindthis를 변경할 수 없습니다.

const arrowFn = () => {
  console.log(this); // 항상 선언 시점의 this (모듈: undefined, 브라우저: window)
};

const obj = { name: 'test' };

arrowFn.call(obj);  // 여전히 선언 시점의 this (obj 아님)
arrowFn.apply(obj); // 동일
const bound = arrowFn.bind(obj);
bound(); // 동일 (bind는 새 함수를 반환하지만 this는 변하지 않음)

React에서의 this 패턴

// 클래스 컴포넌트에서 자주 발생하는 this 문제
class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };

    // 방법 1: constructor에서 bind
    this.handleClick1 = this.handleClick1.bind(this);
  }

  // 방법 1: constructor에서 bind
  handleClick1() {
    this.setState({ count: this.state.count + 1 });
  }

  // 방법 2: 화살표 함수 클래스 필드 (가장 권장)
  handleClick2 = () => {
    this.setState({ count: this.state.count + 1 });
  };

  // 방법 3: 렌더에서 바인딩 (렌더마다 새 함수 생성 — 성능 비효율)
  render() {
    return (
      <div>
        <button onClick={this.handleClick1}>클릭 1</button>
        <button onClick={this.handleClick2}>클릭 2</button>
        <button onClick={() => this.handleClick1()}>클릭 3 (비효율)</button>
      </div>
    );
  }
}

상황별 this 정리

호출 방식this
일반 함수 (non-strict)전역 객체 (window)
일반 함수 (strict mode)undefined
메서드 obj.fn()obj
new fn()새 인스턴스
fn.call(ctx)ctx
fn.apply(ctx)ctx
fn.bind(ctx)()ctx
화살표 함수상위 스코프의 this (고정)
이벤트 핸들러 (일반 함수)이벤트가 발생한 DOM 요소

this를 올바르게 다루는 핵심은 "함수가 어떻게 호출되는가"를 항상 의식하는 것입니다. 화살표 함수를 적절히 활용하면 많은 this 관련 버그를 예방할 수 있습니다.