[JS] 클로저와 실행 컨텍스트

ziwon.k·2021년 10월 5일
1

[JS] 자바스크립트

목록 보기
4/9
post-thumbnail

클로저 학습 후, 실행 컨텍스트를 구성하는 컴포넌트의 생명 주기가 다를 수있다는 사실을 알게되었다.
1. 클로저가 어떻게 상위 함수의 식별자를 참조할 수 있게 되는지
2. 그 때의 실행 컨텍스트 흐름(생명주기)은 어떻게 되는지
위 두 가지를 확실히 학습하는 것을 목표로 정리해보고자 한다.


1.클로저

MDN에서는 클로저를 이렇게 정의하고 있다.

클로저는 함수와 함수가 선언된 렉시컬 환경과의 조합이다.

이게 무슨 말일까?

var x = 1;

function outer () {
  const y = 2;

  function inner () {
    const z = 3;
    console.log(x + y + z);
  }
  inner();
}

outer(); // 6

위 예시의 inner 함수는 outer 함수 내부에서 정의되고 호출되었다.
즉, inner 함수의 상위 스코프를 외부 함수인 outer 함수이고, 그렇기 때문에 inner 함수가 outer 함수의 변수인 w에 접근할 수 있는 것이다!

이러한 현상을 바로 렉시컬 스코프라고한다.



2. 렉시컬 스코프

렉시컬 스코프 : 함수의 상위 스코프가 함수 정의가 평가되는 시점에 함수가 정의된 위치에 따라 결정되는 것

즉, 렉시컬 스코프는 함수가 호출되는 위치에 따라 상위 스코프가 결정되는 동적 스코프가 아닌 함수가 정의된 위치에 따라 함수의 상위 스코프가 결정되는 것을 말한다.
실행 컨텍스트에서 배웠듯이, 함수는 함수를 포함하고 있는, 함수의 상위 코드가 평가되는 시점에 평가된다.

만약 함수 선언문으로 선언된 전역 함수일 경우 전역 코드 평가 과정에서 함수 정의가 평가되므로, 전역 함수의 상위 스코프는 전역 코드가 되는 것이다.

그렇다면 어떻게 함수가 평가되는 시점의 상위 스코프를 기억하는 것일까?



3. 함수 객체의 내부 슬롯 [[Environment]]

함수는 자신의 내부 슬롯 [[Environment]]에 자신이 정의된 위치, 즉 상위 스코프를 저장한다.

다시 말해, 함수는 자신의 정의가 평가되어 함수 객체를 생성할 때 자신이 정의된 위치에 의해 결정된 상위 스코프(렉시컬 환경에 대한 참조)를 자신의 내부 슬롯인 [[Environment]] 에 저장한다.

위의 코드를 예로 들면 outer 함수는 전역 코드가 평가될 때, 즉 현재 실행중인 실행 컨텍스트가 전역 실행 컨텍스트일 때 outer 함수 객체가 생성되며 outer 함수의 [[Environment]] 내부 슬롯에 상위 스코프인 전역 스코프를 저장한다.

inner 함수의 경우 outer 함수 코드가 평가되는 시점, 즉 현재 실행중인 실행 컨텍스트가 outer 함수 실행 컨텍스트일 때 inner 함수 정의가 평가되므로 inner 함수의 [[Environmnet]] 내부 슬롯에는 상위 스코프, 즉 outer 함수의 렉시컬 환경에 대한 참조를 저장한다.

갑자기 렉시컬 환경에 대한 참조라는 표현이 나왔는데,
이전에 실행 컨텍스트를 공부하며 렉시컬 환경의 외부 렉시컬 환경에 대한 참조에는 "현재 평가 중인 소스코드를 포함하는 외부 소스코드의 렉시컬 환경"이 저장된다고 배웠다.

결론적으로 함수 객체 내부 슬롯 [[Environment]]에 저장되는 현재 실행 중인 실행 컨텍스트의 렉시컬 환경에 대한 참조가 저장되며, 그것이 바로 상위 스코프다. 또한 함수가 호출되면서 생성되는 함수 렉시컬 환경의 외부 렉시컬 환경에 대한 참조에 저장될 값이기도 하다.

때문에 모든 함수는 함수가 존재하는 한 자신의 상위 스코프를 갖고 있다!



4. 클로저와 실행 컨텍스트

클로저는 함수와 함수가 선언된 렉시컬 환경과의 조합이다.

다시 이 말을 해석해 본다면, 함수가 선언된 렉시컬 환경이란 함수가 선언된 위치의 스코프, 즉 함수의 상위 스코프를 말한다.
위에서 함수 객체는 내부 슬롯 [[Environment]] 에 자신이 정의된 위치의 상위 스코프를 저장하며, 이 값은 함수가 존재하는 한 함께 존재한다고 했다.
결국 모든 함수는 자신의 상위 스코프를 알고있다는 의미로 결국 넓은 의미에서 자바스크립트의 모든 함수는 클로저라고 볼 수 있다. 하지만 모든 함수를 클로저라고 부르지는 않는다.

그렇다면 클로저가 정확히 무엇일까?

const x = 1;

function outer() {
	const x = 10;
	const inner = function () { console.log(x); }
	return inner;
}

const innerfunc = outer();
innerfunc();

위 코드에서 outer() 함수는 호출 시 실행 컨텍스트 스택에 push 된다.
그 후 outer 함수 실행 과정에서 inner 함수가 평가되며 inner 함수는 현재 실행 중인 실행 컨텍스트의 렉시컬 환경에 대한 참조, 즉 자신의 상위 스코프를 [[Environment]] 에 저장한다.

outer함수는 실행이 종료되면 실행 컨텍스트 스택에서 pop 된다.
그렇다면 outer 함수의 렉시컬 환경은 어떨까?

inner 함수의 [[Environment]] 내부 슬롯에 의해 outer 함수의 렉시컬 환경이 참조되고 있으므로 outer 함수의 렉시컬 환경은 가비지 컬렉팅 대상이 되지 않고 사라지지 않는다.

inner 함수의 관점에서는 자신의 상위 스코프를 알고 있기 때문에
상위 스코프의 식별자를 참조할 수 있고, 상위 스코프의 값을 변경할 수 있는 상황이다. inner 함수와 같은 함수를 클로저라고한다.

클로저란
1. 중첩 함수가 상위 스코프의 식별자를 하나이상 참조하며
2. 중첩 함수가 외부 함수보다 더 오래 유지되는 함수

결국 inner 함수에의해 참조되고 있는 outer 함수의 식별자 x는 결국 inner 함수를 통해서만 접근, 변경 할 수 있다.
클로저는 이처럼 자신의 상태를 안전하게 유지하고 특정 함수에게만 상태 변경을 허용하고자 할 때 사용한다.



5. 결론

  1. 클로저는 외부 함수보다 오래 존재하고, 외부함수의 식별자를 1개 이상 참조하고 있는 중첩함수를 말한다.

  2. 실행 컨텍스트와 렉시컬 환경의 생명주기는 동일하지 않다.
    실행 컨텍스트가 실행 컨텍스트 스택에서 제거되더라도 그와 별개로 렉시컬 환경은 남아있을 수 있다. 단, 렉시컬 환경이 존재하기 위해서는 누군가에 의해 참조되고 있어야 한다.

  3. 클로저에의해 상위 스코프의 렉시컬 환경이 남아있는 상태라고한다면, 해당 렉시컬 환경에 의해 관리되고 있던 모든 식별자, 함수가 함께 남아있는 것이 아니다. 대부분의 모던 브라우저에서는 클로저에 의해 참조되고 있는 식별자만 해당 렉시컬 환경에 남도록 최적화 되어있다.



실행 컨텍스트만 공부했을 때에는 별다른 의심없이 실행 컨텍스트와 렉시컬 환경이 같은 생명주기를 가질 것이라고 생각했었는데 클로저를 학습하면서 그렇지 않다는 것을 알게되었다. 추가적으로 이전에 학습했던 가비지 컬렉터와도 연관지어 생각해 볼 수 있었다. 여러 개념이 누적될 수록 다양한 관점에서 연관지어 생각해보는 것이 도움이 되는 것 같다.

profile
Frontend-Devloper

0개의 댓글