< Closure?! feat. 렉시컬 환경? 언어?>

강민수·2022년 4월 16일
0

근본 시리즈

목록 보기
3/7

오늘은, 자바스크립트와 관련한 개념에 관해 한 번 끄적여 볼까 한다.
자바스크립트를 공부해 본 사람이라면 한 번쯤은 꼭 듣는

CLOSURE.

처음에는 용어 그대로 직역해 봤을 때, 뭔가 닫는 건가? 아니면 가까운 건가? 라는 어렴풋 한 감만 잡고 들어갔다. 물론, 결론적으로 얘기하면, 후자의 의미에 더 가깝기는 하다.

자~ 천천히 하나씩 클로저가 무엇인지 한 번 필자와 함께 살펴봅시다.

1. 사전적 의미.

사실 필자 역시 사전적 의미를 별로 좋아하지는 않지만, 그래도 나름의 정의가 붙어져서 쓰이는 이유가 있을 것이기에...

한 번 정의만 간단히 적어본다.

자신이 선언될 당시의 환경을 기억하는 함수

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

해당 함수의 생명 주기가 종료되더라도 함수의 반환된 값이 변수에 의해 아직 참조되고 있다면 생명 주기가 종료되더라도 (실행 컨텍스트 스택에서 푸시되더라도) 렉시컬 환경에 남아 참조가 가능하다.

처음 이 말들을 들으면... 진짜 뭔 소리인지 이해하지 못 할 것이라는 것을 안다. 그래서 이제부터 렉시컬 환경은 뭔지, 그리고 참조가 뭔지 등등에 대해 직접 사례와 함께 살펴 보자. (직접 봐야 속이 풀리는 타입이라... 🍺)

2. Lexical Environment(어휘적 환경)

먼저, 렉시컬 환경에 대해 먼저 알고 넘어가야 한다.

한국어로 이해하는 것보다는 영어로 정의된 것이 가끔은 더 이해가 잘 될 때가 있다. 필자의 생각에는 이 어휘적 환경 역시 그렇다고 생각한다.

간단히, 요약하자면, 렉시컬 환경은 식별자 변수를 감싸는 데이터 구조라고 보면 된다. 즉, 보통 변수가 참조하는 범위의 위계질서라고 생각하면 되겠다.

다음과 같이 one이라는 변수가 있다. 그리고 addOne이라는 함수가 있다.

이때 이 함수와 변수의 렉시컬 환경에 대해 한 번 고민해 보자.

고민해 보셨다면, 다음과 같다. 먼저, 가장 최상단 부에선, 변수 one은 아직 초기화 되지 않았기에, 사용이 불가하다. 이에 반해, addOne 함수는 함수로서 지정되어 있기에 사용 가능하다. 즉, 다음처럼 렉시컬 환경 상에서 현재 참조 가능하거나 사용가능한 상태를 표현하면 다음과 같다. 즉, 렉시컬 환경 상에서 one은 사용 불가, addOne은 불가다.

자~ 이제. 실행 컴퓨터가 다음 줄을 읽었다. 그러면서 다음과 같이 렉시컬 환경도 바뀌었다.

보이시는 가? 이제. 렉시컬 환경에서 one이 드디어 undefined로 찍힌다. 이것은 무엇을 의미하는가? 초기화가 되었고, 사용 가능한 것이다.

이제 다음 줄로 다시 가서, 할당이 되기 때문에, one에는 할당된 값 1이 담긴다.

그리고 이제 대망의 함수 차례가 왔다. 함수가 실행되면 그 순간, 새로운 렉시컬 환경이 만들어 진다.


다음과 같이 addOne이 인자5를 담고 실행한 순간. 함수안의 인자로 num은 5라는 렉시컬 환경을 갖는다.

아래 표시된 곳에는 함수가 넘겨 받은 매개변수와 지역변수들이 저장 된다.

따라서, 결국 함수가 호출되는 동안, 함수에서 만들어지는 내부 렉시컬과 외부에서 받은 전역 렉시컬 환경 두 개를 가진다.

이에 따라, 내부 렉시컬 환경은 외부 렉시컬 환경에 대한 참조를 갖는다. 지금은 저 함수에 대한 외부 렉시컬 환경이 전역 렉시컬 환경이다.

말이 막 헷갈릴 수 있는데....

그냥 속 편하게 코드에서 변수를 찾을 때, 내부에서 찾고 없으면 외부, 거기도 없으면 전역까지 범위를 넓혀서 찾는다라고 이해하면 쉽다.

이 코드에서 저 원과 넘은 내부 렉시컬에서 찾는다.

넘은 찾았으나, 원이 없다. 이러면 외부로 넓혀서 찾게 된다.

3. 렉시컬 환경 추가 사례.

이제 조금 함수를 복잡하게 더 만들어 보겠다. 다음 예제를 살펴 봅시다.

최초 실행시, 전역 렉시컬 환경을 살펴보면 메이크 에더 함수와 변수 에드3은 전역에 들어간다. 다만, 아직 add3은 초기화는 되지 않은 상태다.

아래 라인이 실행되었을 때, 메이크 에더가 실행되고, 메이크에더의 렉시컬 환경이 만들어 진다. 그러면서 인자값 x에 3이 할당 되었다.


이제 함수가 실행되었으니, 전역 렉시컬의 에드3은 리턴하는 함수로서 들어간다.


Add3 함수를 실행하면, 저 리턴하는 함수가 실행된다. 이때 또, 새로운 렉시컬 환경이 발생한다. 이번엔 y가 2로 들어간다.

이때 참조 구조를 다시 살펴보면,

처음에는 익명함수 렉시컬에서 x와 y를 찾는데, x가 없으니, 참조하는 외부 렉시컬로 가서 x를 찾는다.


즉, y를 가지고 있으면서, 상위 함수를 참조해서 변수 x를 가지고 연산된 결과 값이 콘솔에 리턴 된다.

결국 add3 함수가 생성된 이후에도 상위 함수의 변수에도 접근은 계속 가능한 구조다.

따라서 정리하자면 다음과 같다.

이렇게 내부 함수가 외부함수에 접근이 가능한 것을 클로저라고 한다.
외부함수가 실행이 끝나서 외부함수가 소멸된 이후에도, 내부 함수가 외부 함수의 변수에 접근할 수 있다.


메이크 에더와 에드3은 서로 다른 환경을 가지고 있는 것을 볼 수 있다.

4. 클로저의 사용 이유.

이제 클로저가 무엇인지는 알았다. 그러면, 왜 사용하는 지에 대해서 이제 알아볼 차례다.

클로저는 상태(state)를 안전하게 변경하고 유지하기 위해 사용한다.
다시 말해, 상태가 의도치 않게 변경되지 않도록 상태를 안전하게 은닉(information hiding)하고 특정 함수에게만 상태변경을 허용하기 위해 사용한다.

이 말 역시 잘 이해가 안 될 수 있다. 그래서 다음과 같은 사례를 한 번 준비해 봤다.

다음처럼, counter를 정의해 놓고, 실행해 봤다. 아래 숫자들은 수정이 불가하다. 오직 카운터를 증가시키고 반환시킨다. 즉, 은닉화에 성공한 것이다.이렇게 특정 함수에게만 상태 변경을 허용하기 위해 많이 사용되는 개념이다.

5. 클로저의 생성법.

다음과 같은 경우에 대표적으로 클로저가 생성되고, 사용할 수 있다.

  1. 내부(중첩) 함수가 익명 함수로 되어 외부 함수의 반환값으로 사용될 때
  2. 내부(중첩) 함수가 외부 함수의 스코프에서 실행될 때
  3. 내부 함수에서 사용되는 변수가 외부 함수의 변수 스코프에 포함되어 있을 때

마지막 예시로 한 번더 살펴보자.

var name = `Global`;
function outer() {
  var name = `closure`;
  return function inner() {
    console.log(name);
  };
}

var callFunc = outer();
callFunc();

위 코드에서 callFunc를 클로저라고 한다. callFunc 호출에 의해 name이라는 값이 console 에 찍히는데, 찍히는 값은 Global이 아니라 closure라는 값이다. 즉, outer 함수의 context 에 속해있는 변수를 참조하는 것이다. 여기서 outer 함수의 지역변수로 존재하는 name변수를 free variable(자유변수) 라고 한다.

이처럼 외부 함수 호출이 종료되더라도 외부 함수의 지역 변수 및 변수 스코프 객체의 체인 관계를 유지할 수 있는 구조를 클로저 라고 한다.

6. 정리.

클로저. 사실 우리가 은연중에 짜 놓은 코드를 다시 살펴보면 이런 클로저 구조가 많다는 것을 알 수 있다. 물론 무심결에 써오고 있었지만, 명확히 개념을 알고 쓰는 것과 아닌 것은 엄연한 차이가 있다.

이번 기회를 통해 조금이라도 클로저와 더 가까워 졌으면 한다. 그럼 또 공부하다가 정리할 것들이 생기면 적으러 돌아오겠다.

profile
개발도 예능처럼 재미지게~

0개의 댓글