본문 바로가기

JS

(Javascript) 실행 컨텍스트, 어휘 환경, 클로저

클로저함수, 고차함수, 재귀함수들을 공부하다보니 다소 유사한 개념이 반복되고 있다. 동시에 해당 개념의 차이에 대하여 알아보니 자바스크립트의 실행 컨텍스트, 어휘 환경에 대한 개념을 먼저 알아야지 좀 더 정확한 이해가 가능하여 그 부분의 내용을 정리하려고 한다. 참고로 실행 컨텍스트는 scope, hoisting, this, function, closure 등의 동작원리를 담고 있기 때문에 JS에서 핵심적인 동작원리라고 할 수 있다.

 

1. 실행 컨텍스트란 무엇입니까?

우리는 일정한 공간에 코드를 작성한다. 그 공간을 실행 컨텍스트(Execution Context, 이하 EC)라고 한다. EC는 실행 가능한 코드가 실행될 수 있는 환경을 의미한다. 함수가 호출되면 새로운 EC가 생성되고 컨텍스트 스택에 쌓인다.  기본적으로 스택에는 이미 전역EC가 존재하고 그 위에 쌓이는 것이다. 함수가 여러 개 호출되면 스택(메모리 저장공간)에 계속해서 쌓이게 된다. 함수는 일정한 값이나 함수를 리턴하고 나면 종료되고 맨 마지막에 푸쉬된 EC부터 차례대로 실행되고 실행이 끝나면 앞 전에 쌓인 EC에 실행 제어권(컨트롤)을 반환하며 사라지고 최종적으로 전역EC만 남는다. 마치 자료구조의 스택처럼 후입선출(LIFO)되는 구조이다.  (참고1, 참고2, 참고3)

함수 a는 재귀적으로 자신을 호출한다. a가 호출될 때마다 새로운 EC가 생성되고 EC스택에 저장되게 된다. 다만 메모리 스택 저장공간이 한정되어 있기 때문에 저장량을 초과하게 되면 RangeError가 발생하는 것이다.

 

2. 어휘적 환경

EC는 3가지 영역으로 구분된다. VariableEnvironment, LexicalEnvironment, ThisBinding이다. VE와 LE는 유사하다. VE는 현재 EC내의 변수에 대한 정보를 담고 있기 때문에 선언 시점의 정보를 찍어두는 것들이다.  LE는 EC의 변경사항들이 실시간으로 반영되는 영역으로 처음에는 VE와 동일하다. TB는 변수가 가리키는 대상을 의미한다.

 

여기서 중요한 건 LE인데 LE내부는 EnvironmentRecord와 OuterEnvironmentReference로 구성되어 있다. ER에 의해 호이스팅이 발생하고 OER로 인하여 스코프와 스코프 체인이 형성된다. 코드가 실행되기 전에 자바스크립트 엔진은 EC에 속한 변수명을 모두 알고 있게 된다. 그때 코드 안에 선언된 변수나 함수는 실제로 ER에 저장된다. 그래서 LE는 변수, 함수 이름과 연관된 값을 계속해 추척한다. 

 

func1함수를 선언하면 LexicalEnvironment는 아래와 같다.

function func1() {
  let a = 1;
  return function func2 (b) {
     return a * b;
  }
 }
 
 func1()(2);
 
 //func1이 호출되면 새로운 EC은 아래와 같다.
 execution_environment : {
  LexicalEnvironment : {
   a = 1;
   func2 : function() { }
   },
   ThisiBinding : ...
  }

어휘적 환경은 바로 위와 같이 실행 컨텍스트에서 확인할 수 있으며 EC에 의해 변수(함수도 포함)들을 참조할 수 있는지 여부를 계속해서 찾게 된다. 이때 LE안에서 변수를 참조할 수 있는 유효적인 범위가 정해지게 되며 LE안에 있는 func2는 유효범위 안에 있는 변수 a를 참조해서 자신의 함수 안에서 사용하게 되는 것이다.

이것이 바로 클로저 함수의 정의 MDN 기준 함수와 함수가 선언된 어휘적 환경의 조합이 되는 것이다.. 그리고 일반적으로 클로저 함수를 외부함수의 변수에 접근할 수 있는 내부함수라고 정의하는 것도 위의 LE를 표시한 EC에서 확인할 수 있다.

 

3. 클로저 함수

func1()(2);를 살펴보면 호출연산자가 두번 사용되었다. func1()에 의해 변수 a가 선언되고 함수를 호출하는데 이때 호출되는 것에 반응하는 함수가 func2이다. 이 func2 함수를 func1함수의 콜백함수라고 한다. 그리고 이 func2 함수는 클로저 함수로 func1 함수에서 선언된 변수를 자신의 함수 안에서 참조할 수 있다. 즉 func2 함수의 유효적 범위(스코프)는 외부함수인 func1 까지라는 의미이다. 이렇게 함수 func2가 접근할 수 있는 범위를 닫는 것이(closure:폐쇄) 클로저 함수이다.

 

위의 코드에서 나온 함수 func1과 func2를 가지고 설명을 하면 클로저 함수는 func1과 func2를 모두 가리키는 말이다. 클로저 함수의 특징이 이 2가지 함수에 담겨있다. 함수 func1은 함수 func2를 반환한다. 클로저 함수의 첫번째 특징은 함수를 결과값으로 가진다는 것이다. 그리고 함수 func2는 함수func1의 변수에 접근하여 참조할 수 있다. 즉 외부함수의 변수에 접근할 수 있는 내부함수인 클로저 함수의 두번째 특징인 것이다.

 

지금까지 실행 컨텍스트, 어휘적 환경 그리고 클로저 함수의 정의까지 살펴보았다. 최대한 내가 찾아보고 이해한 것을 쉽게 쓰려고 하였다.  위의 개념들을 알기 위해서 이것저것 찾다보면 내용이 너무 방대하고 내용이 결말이 나지 않고 꼬리에 꼬리를 물어 정확한 개념 이해없이 다양한 정보만 머리에 가득차게 되는 맹점이 발생하였다. 다른 이들은 그런 맹점을 겪지 않고 프로그래밍을 올바르게 할 수 있는 수준까지의 배경지식 차원에서 이해를 해야지만 해당 과제에 대한 적절한 지식과 이해도를 가지고 실무에 임할 수 있을 것으로 생각된다.