실행 컨텍스트와 클로저

LNSol·2022년 10월 9일
1

Javascript

목록 보기
1/6

자바스크립트를 공부하면서 꽤 새로웠던 내용이 실행 컨텍스트였다. 처음에 이게 무슨 말인가 싶었는데 이건 정말 너무 중요한 내용이다! 클로저를 이해하기 위해서도 실행 컨텍스트는 반드시 이해하고 넘어가야 하는 중요한 내용이라고 생각한다. 실행 컨텍스트를 모르고서 클로저를 이해할 수 없다고 생각한다. 중요한 만큼 다시 되짚어보며 정리하려고 한다.

실행 컨텍스트를 생성하는 4가지 타입의 소스코드는 다음과 같다.

  1. 전역코드
    전역에 존재하는 소스코드. 함수, 클래스 등 내부 코드는 포함되지 않는다.

  2. 함수 코드
    함수 내부테 존재하는 소스코드. 함수 내부에 중첩된 함수, 클래스 등의 내부 코드는 포함되지 않는다.

  3. eval 코드
    빌트인 전역 함수인 eval 함수에 인수로 전달되어 실행되는 소스코드.

  4. 모듈 코드
    모듈 내부에 존재하는 소스코드. 모듈 내부의 함수, 클래스 등의 내부 코드는 포함되지 않는다.



소스코드 평가와 실행

자바스크립트 엔진은 소스코드를 ‘소스코드 평가’와 ‘소스코드 실행’ 과정으로 나누어 처리한다.

소스코드 평가 과정에서는 실행 컨텍스트를 생성하고 변수, 함수 등의 선언문만 먼저 실행하여 생성된 변수나 함수 식별자를 키로 실행 컨텍스트가 관리하는 스코프인 렉시컬 환경의 환경 레코드에 등록한다. 소스코드 평가 과정이 끝나면 선언문을 제외한 소스코드가 순차적으로 실행되기 시작한다. 즉, 런타임이 시작된다. 이때 소스코드 실행에 필요한 정보, 즉 변수나 함수의 참조를 실행 컨텍스트가 관리하는 스코프에서 검색해서 취득한다. 그리고 변수 값의 변경 등 소스코드의 실행 결과는 다시 실행 컨텍스트가 관리하는 스코프에 등록된다.

내가 그린 실행 컨텍스트 1

let fn;
{
	const privateUser = { id: 1, name: 'Hong' };
	fn = function() {
		return privateUser;
	}
}
const hong = fn();
hong.age = 30;
console.log(hong);

Global Lexical Environment (전역 렉시컬 환경)

— 전역의 Outer Lexical Environment Reference는 null이다.

— Object Environment > Biding Object는 window 객체를 가리킨다.

— Declarative Environment Record (선언적 환경 레코드)는 let, const 키워드로 선언한 변수가 담긴다.

— 전역에 선언된 fn 변수에 담긴 함수는 블록 내에서 정의 되었다. 따라서 fn 함수의 [[Environment]] 내부 슬롯은 Block Lexical Environment을 가리킨다.


Block Lexical Environment (블록 렉시컬 환경)

— Outer Lexical Environment Reference는 블록이 위치한 전역의 렉시컬 환경을 가리킨다.

— Delarative Environment Record엔 const 키워드로 선언된 privateUser가 담겨있다.

— privateUser는 객체를 담고있고, 객체는 Heap 영역에 저장된다.

— privateUser를 리턴하므로 전역의 Declarative Environment Record의 hong은 Heap에 위치한 객체를 가리킨다.


** Lexical Environment의 Outer Lexical Environment Reference가 자기 바로 바깥쪽의 렉시컬 환경을 가리키며 이것이 스코프 체인이다. **



내가 그린 실행 컨텍스트 2

function getFn() {
	const pv = 5;
	return function(n) {
		return pv + n;
	}
}
const defVal = 100;
const add5Fn = getFn();
console.log(add5Fn(30) + defVal);

Global Lexical Environment (전역 렉시컬 환경)

— Declarative Environment Record에는 const 키워드로 선언된 defVal, add5Fn이 등록된다.

— Binding Object는 window 객체를 가리키며 window 객체에는 전역에서 정의된 함수인 getFn이 등록된다.

— getFn 함수는 전역에서 정의되었으므로 [[Environment]] 내부슬롯은 전역 렉시컬 환경을 가리킨다.

— add5Fn는 소스코드 실행 과정에서 getFn 함수가 반환하는 함수를 가리키게 된다.


Function(getFn) Lexical Environment (함수(getFn) 렉시컬 환경)

— Outer Lexical Environment Reference는 함수의 [[Environment]] 내부슬롯이 가리키는 렉시컬 환경을 가리킨다. 즉 전역 렉시컬 환경을 가리킨다.

— Declarative Environment Record에는 pv가 등록된다.

— 함수를 정의하고 리턴하므로 정의된 함수 객체가 생성된다. 이 리턴되는 함수의 [[Environment]] 내부슬롯은 정의된 현재 위치, 즉 getFn Lexical Environment을 가리킨다.


Function(add5Fn) Lexical Environment (함수(add5Fn) 렉시컬 환경)

— Outer Lexical Environment Reference는 함수 객체의 [[Environment]] 내부슬롯이 가리키는 위치를 가리킨다. 즉, getFn 함수의 렉시컬 환경을 가리킨다.

— 렉시컬 환경 레코드에 매개변수 n이 등록된다.

— pv값과 n의 값을 더한 결과를 리턴하는데 pv는 현재 add5Fn 함수의 렉시컬 환경에 존재하지 않는다. 따라서 Outer Lexical Environment Reference가 가리키는 외부 렉시컬 환경을 차례대로 참조한다. 스코프 체인을 따라가 getFn 함수의 선언적 환경 레코드에 등록된 pv 값을 사용할 수 있다.



클로저 (Closure)

MDN에서는 클로저를 이렇게 정의한다.

“A closure is the combination of a function and the lexical environment within which that function was declared.” - MDN


“클로저는 함수와 함수가 선언된 어휘적 환경의 조합이다. 클로저를 이해하려면 자바스크립트가 어떻게 변수의 유효범위를 지정하는지를 먼저 이해해야 한다.” - MDN


MDN의 정의만 봐서는 클로저를 이해할 수 없었다… 실행 컨텍스트를 그려보고 나서야 내가 내린 클로저의 정의는 이렇다.

“반환되는 함수(B라고 함)의 생명주기가 그 함수가 정의된 함수(A라고 함)의 생명주기보다 길 때 B가 A의 변수를 참조한다면 B 함수는 클로저다.” - LNSol(임은하)

내가 그린 실행컨텍스트 2를 보자. 위 내가 내린 정의에서 A에 해당되는 getFn 함수는 그 함수가 리턴하는 함수보다 생명주기가 짧다. 그리고 반환되는 함수 (add5Fn이 가리키는 함수(B))는 getFn의 렉시컬 환경에 존재하는 pv를 참조하고 있다.


getFn이 호출되어 실행된 후 실행 컨텍스트 스택에서 getFn의 실행 컨텍스트는 pop된다. 하지만 실행 컨텍스트에서 pop된다고해서 바로 렉시컬 환경이 사라지는것은 아니다. 참조되지 않는다면 시간이 지난 후에 GC에 의해 사라지겠지만 현재 add5Fn이 getFn의 렉시컬 환경을 참조하고 있다.


따라서 getFn의 렉시컬 환경에 존재하는 pv 변수에 직접적으로 접근할 수 있는 방법은 없다. add5Fn 함수를 통해 참조만 가능할 뿐이다. 자바스크립트에서는 이렇게 클로저를 이용해 은닉성을 제어한다. 이렇듯 실행 컨텍스트에서 pop된 렉시컬 환경에 존재하는 변수를 참조할 수 있는 add5Fn 함수는 클로저라고 할 수 있겠다.

나를 힘들게 한 클로저. 내가 이겼다.👍

1개의 댓글

comment-user-thumbnail
2022년 11월 24일

우와.. 정리가 너무 잘되어있어요
클로져 이기신것 축하드립니다!!

답글 달기