본문 바로가기

개념이해/javascript

JavaScript this 바인딩 이해하기

핵심 개념

JavaScript에서 this는 함수가 어떻게 호출되느냐에 따라 결정됩니다. 함수를 작성할 때가 아니라, 실행될 때 결정된다는 게 핵심입니다.

1. 기본 바인딩 (Default Binding)

함수를 그냥 호출하면 this는 undefined가 됩니다 (strict mode/모듈 환경).

function sayName() {
  console.log(this.name);
}

sayName(); // TypeError: Cannot read properties of undefined

2. 암시적 바인딩 (Implicit Binding)

객체의 메서드로 호출하면 this는 .앞의 객체를 가리킵니다.

const person = {
  name: '철수',
  sayName: function() {
    console.log(this.name);
  }
};

person.sayName(); // '철수'

// 하지만 함수를 변수에 할당하면 연결이 끊어집니다
const sayNameFunc = person.sayName;
sayNameFunc(); // TypeError (this를 잃어버림)

핵심: 함수를 어떻게 호출하느냐가 중요합니다!

3. 명시적 바인딩 (Explicit Binding)

call, apply, bind를 사용해 this를 직접 지정할 수 있습니다.

call vs apply vs bind

const person1 = { name: '철수' };
const person2 = { name: '영희' };

function introduce(age, city) {
  console.log(`저는 ${this.name}이고, ${age}살이며, ${city}에 살아요`);
}

// call: 즉시 실행, 인자를 하나씩 전달
introduce.call(person1, 25, '서울');
// "저는 철수이고, 25살이며, 서울에 살아요"

// apply: 즉시 실행, 인자를 배열로 전달
introduce.apply(person2, [30, '부산']);
// "저는 영희이고, 30살이며, 부산에 살아요"

// bind: 즉시 실행 안 함! this가 고정된 새 함수를 반환
const introducePerson1 = introduce.bind(person1);
introducePerson1(25, '서울');
// "저는 철수이고, 25살이며, 서울에 살아요"

차이점:

  • call/apply: 즉시 실행
  • bind: this가 고정된 새로운 함수 반환 (이벤트 핸들러에 유용)

bind의 특별한 점

한번 bind로 고정하면 절대 변경할 수 없습니다.

const person = {
  name: '철수',
  sayName: function() {
    console.log(this.name);
  }
};

const anotherPerson = { name: '영희' };

const boundFunc = person.sayName.bind(person);
boundFunc.call(anotherPerson); // '철수' (영희가 아님!)

4. 화살표 함수의 Lexical This

화살표 함수는 자신만의 this를 만들지 않습니다. 대신 정의된 위치의 상위 스코프의 this를 사용합니다.

const person = {
  name: '철수',
  
  // 일반 함수: this = person
  sayName1: function() {
    console.log(this.name); // '철수'
  },
  
  // 화살표 함수: this = 전역 (객체 리터럴은 스코프가 아님)
  sayName2: () => {
    console.log(this.name); // undefined
  },
  
  // 일반 함수 안의 화살표 함수: 상위 함수의 this 물려받음
  sayName3: function() {
    const inner = () => {
      console.log(this.name); // '철수'
    };
    inner();
  }
};

왜 sayName3은 동작할까요?

sayName3: function() {  // ← 이 함수의 this = person
  const inner = () => {  // ← 화살표 함수는 바로 위 함수의 this 사용
    console.log(this.name);  // ← person.name
  };
}

화살표 함수는 "메아리"처럼 바깥 함수의 this를 그대로 따라합니다.

5. this 바인딩 우선순위

여러 바인딩이 충돌할 때 우선순위는 다음과 같습니다:

  1. new 바인딩 - new Person() (가장 강력)
  2. 명시적 바인딩 - bind() > call/apply
  3. 암시적 바인딩 - obj.method()
  4. 기본 바인딩 - 그냥 func() (undefined)
  5. 화살표 함수 - 우선순위 무시, 렉시컬 스코프만 봄

6. React 클래스 컴포넌트에서의 this

React 클래스 컴포넌트에서 this 문제가 자주 발생합니다.

class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
    
    // 방법 1: constructor에서 bind
    this.handleClick1 = this.handleClick1.bind(this);
  }
  
  // 일반 메서드 (bind 필요)
  handleClick1() {
    this.setState({ count: this.state.count + 1 });
  }
  
  // 일반 메서드 (bind 안 하면 에러!)
  handleClick2() {
    this.setState({ count: this.state.count + 1 });
    // TypeError: Cannot read properties of undefined (reading 'setState')
  }
  
  // 방법 2: 화살표 함수 (권장)
  handleClick3 = () => {
    this.setState({ count: this.state.count + 1 });
  }
  
  render() {
    return (
      <div>
        <button onClick={this.handleClick1}>작동</button>
        <button onClick={this.handleClick2}>에러!</button>
        <button onClick={this.handleClick3}>작동</button>
      </div>
    );
  }
}

왜 handleClick2는 에러가 날까요?

<button onClick={this.handleClick2}>
  1. 버튼에 함수를 전달할 때, 함수만 전달됩니다 (객체와의 연결 끊김)
  2. 버튼 클릭 시 React가 handleClick2()로 호출
  3. 함수 안의 this는 undefined
  4. undefined.setState() → 에러!

화살표 함수는 왜 작동할까요?

handleClick3 = () => {
  this.setState({ ... });
}

이 코드는 실제로는 이렇게 변환됩니다:

constructor() {
  super();
  // 화살표 함수가 여기서 생성됨!
  this.handleClick3 = () => {
    this.setState({ ... });  // constructor의 this = 인스턴스
  };
}

화살표 함수는 constructor 안에서 만들어지고, constructor의 this(= 컴포넌트 인스턴스)를 물려받습니다.

Constructor의 역할

constructor = 초기화 함수

클래스로 객체를 만들 때 자동으로 실행되는 특별한 함수입니다.

class Person {
  constructor(name, age) {
    console.log('constructor 실행!');
    this.name = name;    // 초기값 설정
    this.age = age;
    this.createdAt = new Date();
  }
}

const person = new Person('철수', 25); // constructor 자동 실행!

React에서는:

constructor(props) {
  super(props);  // 부모 클래스 초기화 (필수!)
  this.state = { count: 0 };  // 상태 초기화
  this.handleClick = this.handleClick.bind(this);  // 메서드 바인딩
}

실전 팁

1. React에서 this 문제 해결 방법

// ✅ 권장: 화살표 함수
handleClick = () => {
  this.setState({ ... });
}

// ✅ 괜찮음: constructor에서 bind
constructor() {
  this.handleClick = this.handleClick.bind(this);
}

// ⚠️ 비권장: render에서 화살표 함수 (매번 새 함수 생성)
<button onClick={() => this.handleClick()}>

2. setTimeout에서 this 잃어버리는 문제

// ❌ 문제
setTimeout(person.sayName, 1000); // undefined

// ✅ 해결 1: bind
setTimeout(person.sayName.bind(person), 1000);

// ✅ 해결 2: 화살표 함수
setTimeout(() => person.sayName(), 1000);

3. 요즘은 함수형 컴포넌트!

함수형 컴포넌트와 hooks를 사용하면 this 문제가 완전히 사라집니다.

function Counter() {
  const [count, setCount] = useState(0);
  
  const handleClick = () => {
    setCount(count + 1); // this 걱정 없음!
  };
  
  return <button onClick={handleClick}>+</button>;
}

핵심 정리

  1. this는 호출 방식에 따라 결정됩니다
  2. 암시적 바인딩: obj.method() → this는 obj
  3. 명시적 바인딩: call/apply/bind로 this 직접 지정
  4. 화살표 함수: 자신의 this 없음, 상위 스코프의 this 사용
  5. 우선순위: new > bind > call/apply > 암시적 > 기본
  6. React: 화살표 함수나 bind로 this 고정 필요

this는 처음엔 어렵지만, 호출 방식만 정확히 파악하면 예측 가능합니다!

반응형