Concepts to the closure in javascript
처음부터 클로저에 대해 알아보려 했으나, 클로저 개념을 이해하기 위해서는 우선 javascript에서 소스코드가 평가 및 실행되는 방식에 대해 이해하고 시작하는 것이 더 좋을 것이다.
그럼 먼저 컨텍스트에 대해 알아보자면, 컨텍스트(context)는 특정코드가 실행되는 환경 또는 상황을 의미한다. 이런식으로만 표현하면 딱딱해서 이해하기가 어렵다. 다시 쉽게 설명하자면, 실행정보를 담고있는 무언가라고 이해하면 되는데, 우리가 일반적으로 소스코드를 작성하고 실행(일반적인 의미의 실행)을 하면 js engine에서 소스코드에 대한 평가(evaluation) 이후 실행(execution)을 진행하면서 javascript 프로그램이 된다.
그럼 실행 전에 평가를 왜하냐? 라고 하면 각 소스코드 스코프에 어떤 변수와 실행정보가 있는지 정적으로 분석 후 이를 해당하는 실행컨텍스트에 담기위해 평가의 과정을 거친다. 이후 실제로 실행과정에서는 각 포인트의 실행컨텍스트에 담긴 정보를 참조하여 프로그램을 구동한다고 이해하면 쉽게 이해가 가능하다.
소스코드도 여러개, 소스코드 스코프도 여러개라고 표현했는데, 이는 각각 4개 가량의 종류가 있다.
1. 전역 코드
최상위 스코프인 전역 스코프를 생성해야 하며, var로 선언된 전역변수와 함수선언으로 정의된 전역함수를 전역객체에다 바인딩한다. 전역코드는 평가되면 전역 실행 컨텍스트가 생성된다.
2. 함수 코드
함수 코드는 함수스코프언어인 js 답게 지역스코프를 생성하고, 지역변수, 매개변수, arguments 객체를 관리한다. 지역스코프가 생성된 다음에는 전역스코프의 스코프체인의 하나로 연결이 된다. 이를 위해 함수 실행 컨텍스트가 생성된다.
3. eval 코드
eval 코드는 “strict mode에서 자신만의 독자적인 스코프를 생성한다.” 라고 표현하는데, 이는 추후에 한번 실험을 통해 알아봐야겠다. 위 설명에서 본 것과 같이 eval 실행 컨텍스트를 생성한다.
4. 모듈 코드
우리가 아는 그 require()로 가져와서 사용하는 모듈맞다. 모듈코드는 코드 평가를 거친 후 모듈별로 개별 실행 컨텍스트를 생성한다.
클로저에 대해 알아보자면, 먼저 클로저는 함수형 프로그래밍언어에서 사용하는 특성이다.
MDN에서 말하는 클로저는 “클로저는 함수와 그 함수가 선언된 렉시컬 환경과의 조합이다”라고 설명하고 있다. 이 정의도 마찬가지로 이론적인 정의로는 쉽게 이해가 되지 않는다.
조금 더 표현을 쉽게 만들어보면 “외부함수보다 외부함수 안에 선언된 중첩함수가 더 오래 유지되는 경우에 안에 있는 중첩함수는 외부함수의 생명주기가 종료되더라도 외부함수의 변수를 참조할 수 있는데, 이러한 중첨함수를 클로저라고 한다.”라고 할 수 있다.
이를 잘 이해하기 위해서는 위에서 설명한 컨텍스트 개념을 접목시켜서 이해하면 도움이 되는데, 일단 자바스크립트는 함수를 사용하는 위치보다 선언하는 위치가 상위스코프를 결정짓는 데 활용된다.
그래서 outer()라는 외부함수안의 inner() 라는 함수는 사용되는 곳이 어디든 상관없이 상위스코프는 선언위치의 상위함수인 outer() 함수의 스코프를 외부스코프로 참조한다.
아래 코드를 보면, 전역변수로 x는 1로 할당되어 있고, foo() 함수 내에서는 지역변수 x로 10이 할당되어 있다.
중첩된 bar() 함수는 x를 출력하며, foo()함수는 이러한 bar 함수를 반환한다.
하단의 전역코드는 foo()함수를 통해 bar함수를 반환받고, 이를 변수에 저장시켜, barFunc()으로 함수실행을 진행한다.
그런데, 어이쿠? 결과 값은 함수실행이 이루어지는 위치인 전역블록에 저장된 x의 1이 아닌 10이 출력된다.

왜 이런 현상이 벌어지는 걸까?
이는 javascript가 내부적으로 실행되는 과정을 들여다보아야 하는데, 일전에 이야기한 promise와 관련하여 javascript는 평가와 실행을 하는 시점에서 실행 컨텍스트 스택(콜 스택)에 현재 실행하는 컨텍스트에 대한 푸시와 팝을 통해 실행을 이어나간다고 설명을 했었다.
- [[javascript 탐닉] try/catch, promise 알아보기 [https://stockofjobless.tistory.com/81]
그럼 더더욱 foo()는 컨텍스트 스택에서 pop되는거니까 foo() 내부의 x는 참조하지 못하는거 아니야? 라는 질문을 할 수 있다.
맞다. 반만 맞다. 컨텍스트 스택에서 pop되는 건 맞지만, foo() 함수를 실행과 동시에 barFunc에 할당시키는 과정에서 내부적으로 bar 함수는 스코프 간 연결을 유지하게 된다. 원래대로라면, 컨텍스트가 pop되면서 스코프체인도 같이 끊겨야 하지만, 위와 같은 코드는 bar 함수가 클로저 역할을 하게되면서 함수선언 당시에 참조하고 있는 x로 인해 스코프가 소멸되지 않고 계속 살아있는 것이다.
이러한 이유로 위 코드에서는 소멸된 줄로만 알았던 내부변수 x는 여전히 살아있고, 그리하여 10이 출력되는 것을 확인할 수 있는 것이다.
그럼 이러한 클로저는 어떻게 활용할 수 있느냐 하면, 위 코드처럼 한번 foo의 생명주기(Life cycle)이 끝나면 전역객체 입장에서는 내부변수 x에 대한 접근이 어렵다. 그렇기 때문에 캡슐화와 같은 효과를 가져올 수 있고, 불변성을 더할 수도 있는 부분이다.
클로저는 자바스크립트 뿐만 아니라 다양한 함수형언어에서 흔히 사용하고 있으며, 여기에 고차함수(high order function) 개념을 더하여 부수효과(side effect)를 예방할 수 있다는 이점이 있다.
추후에는 실질적으로 클로저를 활용하여 코드를 더 견고하게 만들어 보도록 하겠다.