본문 바로가기
전공공부

2025.06.15 this의 원리와 예시

by 시아나 2025. 6. 15.

This 바인딩이란?

this를 사용할 때 어떤 객체를 참조할지 정해주는 것이다.

이 this가 상황에 따라 달라질 수 있기 때문에 무척 헷갈리는데 오늘은 this가 어떻게 결정되는지와 헷갈리는 상황들을 봐 볼 것이다.

상황에 따른 this 바인딩

일반적인 함수 호출(동적 바인딩)

this 호출 시점에 동적으로 할당됨

화살표 함수(정적 바인딩)

화살표 함수의 바깥 스코프에서 this를 가져온다.

처음 정의될 때 한번만 결정되기 때문에 정적 바인딩

const obj = {
  name: "A",
  normalFn: function () { /* 일반적인 함수 호출 */
    console.log(this.name); // "A"
  },
  arrowFn: () => { /* 화살표 함수 호출 */
    console.log(this.name); // ❌ undefined
  }
};

obj.normalFn(); // "A"
obj.arrowFn();  // undefined → 전역 스코프의 this를 캡처했기 때문

왜 이렇게 동작할까?

이걸 알기 위해서는 JS의 Call Stack, 실행 컨텍스트에 대해 알아야 된다.

이전에 JS 클로저에 대해 알아보면서 정리해 둔 내용이 있어 살짝 가져와보겠다.

https://ytlive.tistory.com/359

실행 컨텍스트(Execution Context)

실행 컨텍스트(Execution Context)란 실행할 코드에 제공할 환경 정보들을 모아놓은 객체로

JS 엔진의 Call Stack에 담는 단위이다

실행 컨텍스트에는 변수 객체, 스코프 체인, this 값에 대한 정보가 담겨있다.

처음 프로그램을 실행하면 전역 컨텍스트(Global Context)가 콜스택에 담긴다.

실행 컨텍스트 내부 구조

실행 컨텍스트가 어떻게 생겼나 간단히 살펴 보자면

  • Variable Environment (변수 환경) : 초기화 시점의 변수, 함수 선언 정보 저장
    • Environment Record (번수 저장소) : 실제 식별자와 그 값을 저장하는 공간
    • Outer Lexical Environment (스코프 체인) : 상위 스코프(외부 렉시컬 환경)를 참조하여 스코프 체인을 만든다.
      Outer Lexical Environment 은 상위 스코프의 Lexical Environment를 참조한다.
      때문에 현재 스코프에 없는 값이라도 Outer를 타고 올라가서 상위 스코프에 있는 값을 조회할 수 있다.
  • Lexical Environment (렉시컬 환경) : 변수 환경 + 스코프 체인
  • This Binding : 컨텍스트에서의 this 값

으로 이루어져 있다.

우리는 지금 this binding을 살펴보려고 한다.

그래서 왜 이렇게 동작 하는데?

this의 값은 실행 컨텍스트의 this binding 값에 따라 달라지는데 일반적인 함수와 화살표 함수의 동작과정이 다르다.

일반적인 함수는 호출될 때마다 새로운 실행 컨텍스트를 만들고 실행 컨텍스트 내부의 This Binding은 호출 주체에 따라 결정된다.

화살표 함수가 아니면 대부분 일반 함수로 동작한다.

화살표 함수는 실행 컨텍스트를 생성할 때 this 바인딩 단계를 생략하고 상위 스코프의 실행 컨텍스트에 존재하는 this를 그대로 캡처한다.

만약 전역 함수에서 일반적인 함수와 화살표 함수를 호출한다면 아래와 같이 Call Stack이 그려진다.

근데 왜 화살표는 다르지?

원래 JS는 함수형 언어로 시작했지만 객체 지향적인 기능이 추가되면서 함수가 각 객체의 메서드로 동작할 수 있게 되었다.

그래서 함수를 누가 호출되었는지에 따라 동작을 다르게 해야 했기 때문에 this가 다르게 동작해야 되었다.

화살표 함수는 새로운 this 바인딩을 만들지 않기 때문에 콜백 함수에서의 this 문제를 해결할 수 있었다.

  • 일반적인 함수의 콜백 this 문제
// sayName 함수 내부에서 timeout을 줘야 하는 상황
const obj = {
  name: '철수',
  sayName: function() {
    setTimeout(function() {
      console.log(this.name); // => 여기서 this는 철수가 아님
    }, 1000);
  }
};

obj.sayName();  // 출력: undefined

이를 해결하기 위해 기존의 JS에서는 임시 변수를 사용해야 했다.

const obj = {
  name: '철수',
  sayName: function() {
      let self = this
    setTimeout(function() {
      console.log(self.name);
    }, 1000);
  }
};

obj.sayName();   // 출력: 철수

이 과정은 번거롭기도 하고 코드의 가독성을 떨어뜨렸다.

하지만 화살표가 도입되면서 이러한 문제점이 해결되었다.

const obj = {
  name: '철수',
  sayName: function() {
    setTimeout(() => {
      console.log(this.name);
    }, 1000);
  }
};

obj.sayName();  // 출력: 철수

화살표는 외부 함수의 this를 그대로 사용하기 때문에 sayName 내부의 this는 obj의 this이다.

때문에 setTimeout의 this.name은 철수가 될 수 있었다.

헷갈릴 수 있는 this 예시

메서드 안의 this

const person = {
  name: '영희',
  greet: function() {
    console.log(this.name);
  }
};

person.greet();  // 출력: 영희

이 코드의 Call Stack을 보면

greet가 실행될 때 gloal의 person이 실행했기 때문에

global 실행 Context의 Variable Environment가 참조했던 person 객체를 this로 가지게 된다.

메서드 안에서 내부 함수 호출 시 this

const person = {
  name: '영희',
  greet: function() {
    console.log(this.name); // 영희
    function inner() {
      console.log(this.name);
    }
    inner();
  }
};

person.greet();  // 출력: undefined (또는 전역객체의 name)

greet 함수는 객체를 통해 호출했기 때문에 greet 내부의 this는 person을 바인딩 한다.

하지만 inner 함수는 호출하는 객체가 없었기 때문에 this 를 전역 객체(window) 또는 undefined (strick mode)로 바인딩 한다.

생성자 함수에서의 this

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

const p = new Person('철수');
p.sayName();  // 출력: 철수

이 때 sayName은 p가 호출했기 때문에 this binding은 p 인스턴스를 가리킨다.

단독 함수 호출에서의 this

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

showThis();  // 출력: 전역객체 (브라우저는 window, 엄격모드면 undefined)

이 코드는 그럼 showThis를 호출하는 객체가 없기 때문에 전역객체나 undefined를 호출한다.

이벤트 핸들러에서의 this

const button = document.createElement('button');
button.textContent = '클릭';
document.body.appendChild(button);

button.addEventListener('click', function() {
  console.log(this);  // 출력: 버튼 DOM 요소
});

클래스 내부 함수에서의 this

class User {
  constructor(name) {
    this.name = name;
  }
  printName() {
    console.log(this.name);
  }
}

const user = new User('지훈');
user.printName();  // 지훈

다음에 보고 싶은거

  • JS의 비동기

'전공공부' 카테고리의 다른 글

AI에 대한 이해 - LLM이란 뭔가?  (0) 2025.05.18
JavaScript의 클로저와 실행 컨텍스트  (0) 2025.04.08
JavaScript 엔진과 동작원리  (0) 2025.03.31
Command 패턴  (0) 2025.03.29
프론트 버블링에 대하여  (0) 2024.12.12