지난글에서는 함수를 호출하는 형태에 따라서 결정되는 this의 초깃값을 다뤘다.

여기서 말하는 this의 초깃값은 '함수의 호출 직후'~'함수의 실행 컨텍스트가 생성되기 전' 사이에 전달되는 this의 값이다. strict mode나, arrow function의 구분은 이 다음 단계에서 이루어지기 때문에 당장은 신경쓰지 않아도 된다.

  1. 함수 호출이 function object를 통해서 이루어지면, this의 초깃값은 undefined로 정해진다.

  2. 함수 호출이 Reference를 통해서 이루어지면, Reference의 base값에 의해 this의 초깃값이 결정된다.

    • base가 일반 object라면 this는 해당 object가 된다.
    • base가 EnvironmentRecord라면 this의 초깃값은 undefined가 된다.

with statement내부에서 함수를 호출하는 경우는 base가 EnvironmentRecord일때 this의 초깃값이 undefined가 되지않는 유일한 예외다. with은 eval과 함께 죄악..처럼 취급받는 문법이기 때문에 이 글에서는 다루지 않는다.

이번 글에서는 이렇게 초깃값이 결정된 this가 실행 컨텍스트에 어떻게 바인딩되는지, 함수 바디에서 this를 참조할 경우에 this값이 어떻게 결정되는지 알아보도록 하겠다.

this가 실행컨텍스트에 바인딩되는 과정

call expression이 평가되면 this의 초깃값이 결정되고, internal method인 [[call]]을 통해서 실제로 함수가 실행된다.

[[call]]은 함수를 위한 새로운 실행컨텍스트를 생성하고 함수 바디를 실행하는데 이 과정에서 OrdinaryCallBindThis이라는 내부 연산을 통해서 새로운 실행컨텍스트에 this값을 바인딩한다.

OrdinaryCallBindThis의 과정

  1. arrow function이라면 그대로 종료한다. (바인딩 x)
  2. strict mode라면 this값을 초깃값 그대로 사용한다. (객체 or undefined)
  3. strict mode가 아닐 때
    • this의 초깃값이 undefined 또는 null 이라면 this값을 global로 사용한다.
    • this의 초깃값이 undefined 또는 null이 아니라면 [[ToObject]] 내부 연산을 통해 객체로 wrapping한다.
  4. 새로운 실행 컨텍스트의 'FunctionEnvrionmentRecord'에 this값을 저장한다.

복잡해보이지만 (strict mode가 아닐 때) this의 초깃값이 undefined라면 global 객체로 변환된다는 것만 빼면 달라지는 것은 없다.

(arrow function인 경우를 제외하고)

  • this: object -> object
  • this: undefined -> global/window
  • this: (strict) undefined -> undefined

여기서 주목해야할 부분은 결정된 this값을 함수의 실행컨텍스트의 EnvironmentRecord에 직접 저장한다는 것이다.

strict 모드인 경우에 실제로 this는 undefined라는 값으로 바인딩된다. arrow function의 경우에는 애초에 바인딩 자체를 하지 않는다는 점을 기억해두자.

이렇게 EnvrionmentRecord에 바인딩된 this값은 함수 바디에서 this 키워드를 만났을 때 사용된다.

실행 컨텍스트에서 this의 평가 과정

코드를 실행하다가 this 키워드를 만나면 this의 값을 찾기위한 탐색에 들어간다.

프로퍼티를 찾기위해 프로토타입 체인을 검색하거나, 식별자를 찾기위해 스코프 체인을 검색하는 것과 같은 과정을 거치는데

우선 현재 실행컨텍스트의 EnvironmentRecord를 찾아서 this가 바인딩되어있는지 확인한다.

OrdinaryCallBindThis()에서 보았듯이, arrow function이 아닌 경우에는 this값이 undefined라고 해도 바인딩을 해버리기 때문에 뭐가 됐든 this값을 찾을 수 있다.

this값이 없다면 this값이 바인딩 된 EnvironmentRecord를 찾을 때 까지 상위 스코프를 탐색한다.

이런 경우 lexical scope를 계속 거슬러 올라가다보면 결국 GlobalEnvironmentRecord에 도달하기 때문에, 탐색은 global this인 global/window 객체를 반환하며 종료된다.

var foo = {
  func: function() {
    const nested = () => {
      console.log(this);
    };
  return nested;
  }
};

// 1)
const withFoo = foo.func();
withFoo();  // this === foo 

// 2)
const func = foo.func;
const withoutFoo = func();
withoutFoo(); // this === global

1번의 경우에 nested라는 화살표함수가 만들어질 때는 foo.func()형태로 함수가 실행중이며 this는 foo로 바인딩되어있는 상태다.

따라서 withFoo라는 이름으로 반환된 화살표함수가 this값의 검색을 시작하면 this값이 foo인 foo.func의 EnvironmentRecord를 만나게 된다.

2번의 경우는 func() 형태로 함수가 실행했기 때문에 this는 global로 바인딩되어있는 상태다.

마찬가지로 withoutFoo라는 이름으로 반환된 화살표함수가 this값의 검색을 시작하면 this값이 global인 func의 EnvironmentRecord를 만나게 되는것이다.