Notice
Recent Posts
Recent Comments
Link
«   2026/01   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

likeornament 님의 블로그

this는 왜 스코프 규칙을 따르지 않을까 본문

공부/javascript

this는 왜 스코프 규칙을 따르지 않을까

likeornament 2026. 1. 15. 20:44

실행 컨텍스트에는 세 가지 중요한 정보가 담긴다.

  • VariableEnvironment
    - 현재 컨텍스트 내의 식별자들에 대한 정보와 외부 환경 정보
    - 선언 시점의 LexicalEnvironment의 스냅샷으로, 변경 사항은 반영되지 않음
  • LexicalEnvironment
    - 처음에는 VariableEnvironment와 같지만 변경 사항이 실시간으로 반영됨
  • ThisBinding
     this  식별자가 바라봐야할 대상 객체

스코프는 VariableEnvironment와 LexicalEnvironment를 통해 구현된다.

 

이렇게 구현되는 스코프는 마치 대나무처럼 곧다.
어디서 선언되었는지에 따라 상위 스코프가 정해지고, 한 번 정해지면 결코 변하지 않는다.

 

반면  this 갈대와 같다.
어디서 선언되었는지가 아니라, 어떻게 호출되었는지에 따라 그 정체가 바뀐다.

 

여기서 의문이 생긴다.

실행 컨텍스트가 생성될 때 스코프는 선언 시점을 기억하는데,
ThisBinding은 왜 호출 시점을 기억하도록 설계되었을까?

스코프는 선언 시점 기준으로 결정된다.
그렇다면  this 도 함수가 선언될 때 한 번 정해두면 되지 않았을까?

 

이번 글에서는 this를 스코프처럼 선언 시점에 고정한다는 가설을 세우고,
그 가설의 결과를 확인해보려 한다.


function introduce() {
  console.log(`안녕하세요, 제 이름은 ${this.name}입니다.`);
}

const personA = { name: '철수', sayName: introduce };
const personB = { name: '영희', sayName: introduce };

personA.sayName(); // 안녕하세요, 제 이름은 철수입니다.
personB.sayName(); // 안녕하세요, 제 이름은 영희입니다.

 

함수는  introduce  하나뿐이지만,
호출 주체에 따라  this 가 달라진다.

 

만약  this 가 선언 시점에 고정돼 있었다면  introduce 하나의 객체에만 종속될 수 있었을 것이다.

 

즉, 호출 시점 this 바인딩은 함수 재사용을 가능하게 만들어 준다.

 

이를 통해 우리는 아래의 이점을 취할 수 있다.

  • 메모리 절약
  • 동일한 로직을 여러 객체가 공유하는 유연한 구조
즉,   this는 "함수의 소유자"가 아니라 "이번 호출의 주체"를 표현하기 위해 존재한다는 것을 알 수 있었다.

하지만 문제는 여기서 끝나지 않는다.

 this 가 호출 시점에 바뀌기 때문에 개발자들은 종종 헷갈린다.

 

그렇다면 선언 시점 기준이었다면 더 나았을까?

 

const obj = {
  name: 'obj',
  getName() {
    console.log(this.name);
  }
};

const getName = obj.getName;
getName(); // undefined

 

obj.getName을 떼어내 실행하자 undefined가 출력된다.

 

이는 메서드를 떼어내는 순간 메서드가 아닌 함수로서 실행했기 때문에

함수의 호출 주체가  obj 에서 전역 객체로 바뀌었기 때문이다.

전역 객체에는 name 프로퍼티가 없으므로 undefined가 나온다.

 

만약  this 가 선언 시점에 고정돼 있었다면 getName은 항상  obj 를 바라봤을 것이다.  

 

그렇다면 이런 코드는 어떻게 될까?

const another = { name: 'another' };
another.getName = obj.getName;

another.getName(); // ?

 

선언 시점  this 라면
getName은 여전히  obj 를 바라보게 되고,

another가 아니라  obj 의 이름을 출력했을 것이다.

즉, 선언 시점 this는 예측하기 쉽지만, 객체 간 메서드 공유를 불가능하게 만든다.

여기까지 살펴보니 자바스크립트의 선택이 보인다.

  • 선언 시점 this
    → 안정적이지만 재사용성과 다형성을 잃음
  • 호출 시점 this
    → 혼란스럽지만 강력한 유연성을 얻음

자바스크립트는 클래스 중심 언어가 아니라,
객체들이 서로 기능을 자유롭게 공유하고 빌려쓰는 구조를 지향했다.

 

그 결과  this 를 특정 객체에 묶어두지 않고,
실행 시점에 결정되도록 설계했다.


하지만 이 유연함은 부작용도 낳았다.

const obj = {
  outer() {
    function inner() {
      console.log(this);
    }
    inner();
  }
};
obj.outer();

 

겉보기에는 outer 함수가 메서드로 호출됐고 inner 함수는 그 안에 있으니

같은  this 를 가리킬 것처럼 보인다.

 

그러나 inner의  this 는 전역 객체를 가리킨다.

이 차이는 단 하나에서 나온다.

  • outer: 점(.)을 통해 메서드로 호출
  • inner: 점(.) 없이 함수로 호출

 this 는 "어디에 정의됐는가"가 아니라 "어떻게 호출됐는가"만을 본다.

 

유연함을 얻은 대신, 
 this  이탈이라는 비용을 지불하게 된 것이다.

 

const obj = {
  outer() {
    const inner = () => {
      console.log(this);
    };
    inner();
  }
};
obj.outer();

 

이러한 배경에서 화살표 함수가 등장한다.

 

이 코드에서 inner obj 를 가리킨다.

 

이를 보고 "화살표 함수는 this를 선언 시점으로 바꾼 함수인가?"라고 생각하기 쉽다.

하지만 정확히 말하면 그렇지 않다.

화살표 함수는 실행 컨텍스트에 ThisBinding 자체가 없다.

즉,  this를 바인딩하지 않고 가장 가까운 상위 컨텍스트의  this를 그냥 참조할 뿐이다.

결론

 this 가 호출 시점에 결정되는 이유는 자바스크립트의 결함이나 실수가 아니다.

 

함수의 재사용성, 객체 간 메서드 공유, 다형성과 유연성을 얻기 위해
자바스크립트가 선택한 설계상의 타협이다.

 

그리고 이 유연함이 항상 필요한 것은 아니다.

 

this가 호출 방식에 따라 바뀌지 않기를 원할 때,
자바스크립트는 화살표 함수라는 또 다른 선택지를 제공한다.

 

결국 사용자에게 선택을 요구하는 언어인 자바스크립트를 사용할 때,

상황에 맞는 선택을 스스로 판단하는 능력이 가장 중요하다는 사실을 다시 한 번 느끼게 된다.