본문 바로가기
자바스크립트 중급 강의

클로저(Closure) 5분만에 이해하기 - 자바스크립트 중급 강좌 #11

2021. 11. 2.

자바스크립트 중급 강좌 #11 클로저(Closure) 5분만에 이해하기

 

어휘적 환경 (Lexical Environment)

Lexical 환경 (내부 - 전역)

- 현재 코드는 이제 막 시작하여 노란줄 위치에서 실행되고 있다고 가정하자.

- 스크립트 내에서 선언된 변수들은 lexical 환경으로 올라간다.

이때, let으로 선언한 one 이라는 변수는 호이스팅된다. 단, 현재 위치에서는 초기화가 되어있지 않아서 사용할 수 없을 뿐이다.

반면, 함수 선언은 변수와 달리 바로 초기화 되기 때문에 현재 위치에서도 사용이 가능하다. (변수로 선언된 함수 표현식은 이렇게 사용할 수 없다)

- 현재 위치에서는 변수는 선언되었지만 할당은 되어있지 않기 때문에 one은 초기값 undefined를 갖는다.

값이 undefined일 뿐, 사용해도 에러는 발생하지 않는다.

- 이제 one에 1이라는 값이 할당되었다.

- addOne 함수가 실행되면 이때 새로운 Lexical 환경이 만들어진다.

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

- 함수가 실행되는 동안 함수 내부 Lexical 환경과 외부의 전역 Lexical 환경으로 나뉜다.

- 내부 Lexical 환경은 외부 Lexical 환경을 참조한다.

코드에서 변수를 찾을 때 내부 -> 외부 -> 전역 순으로 범위를 넓혀 찾는다. 

- 위 코드에서 one과 num 을 먼저 내부 Lexical 환경에서 찾는다.

num 은 찾았지만 one은 찾지 못했다.

그러면 범위를 외부 Lexical 환경으로 넓혀서 one을 찾는다.

 

=> 여기까지가 Lexical 환경의 1차적인 설명이다. 

전역 Lexical 환경이 가장 밑단의 1차 레이어로 존재하고,함수가 실행될 때 새로운 Lexical 환경이 2차 레이어로 만들어진다.

함수 내부의 Lexical 환경에서 원하는 변수를 찾지 못하면 외부 -> 전역 순으로 범위를 넓혀가면서 찾는다.

어느 시점에 어떤 Lexical 환경이 생성되고, 필요한 변수를 어디서부터 가져오게 되는지를 이해하는 것이 중요하다.

 

Lexical 환경 (내부-외부-전역)

- 지금까지는 함수의 실행으로 생기는 내부 Lexical 환경과 전역 Lexical 환경만 고려하면 됐다.

레이어로 표현하자면 2겹이 있었던 셈이다.

근데 만약, 함수가 함수를 만드는 식으로 레이어들이 겹겹이 쌓이면 어떻게 될까. 

변수의 값은 어디서 가져오게 되는 걸까.

- 아래 예시의 add 함수와 add 함수를 만드는 makeAdder 함수를 보면서 이해해보자.

- 코드 최초 실행 시, 함수 makeAdder 는 바로 초기화되어 사용 가능하고, 변수 add3 은 호이스팅은 됐지만 초기화되지 않아서 사용할 수 없는 상태로 전역 Lexical 환경이 만들어진다.

- 현재 위치에서 코드가 실행되면 makeAdder 함수가 실행되고, 해당 함수의 Lexical 환경이 만들어진다.

- 함수의 Lexical 환경에는 넘겨받은 매개변수와 지역변수들이 저장되므로, x 는 3으로 저장된다.

- add3 은 makeAdder 함수가 실행되면서 return 한 함수가 된다.

- 마지막 줄에서 add3 을 실행하면 add3 에 저장한 익명함수가 실행되고 이때 Lexical 환경이 만들어진다.

함수에 전달된 값에 따라 y 는 2 라는 값을 갖는다.

- 익명함수가 실행되면서 x + y 를 할 때, 먼저 y는 함수 내부의 Lexical 환경에서 2라는 값을 찾았다.

x는 찾지 못했고, 해당 함수 외부의 Lexical 환경에서 x 는 3 이라는 값을 찾아서 최종적으로 x+y 연산을 하게 된다.

- 여기까지 어떻게 보자면 3겹짜리 Lexical 환경이 겹쳐있다고 보면 된다.

add3 를 실행하면 add3 내부에서 변수를 먼저 찾고, 그 다음으로 add3를 생성한 함수인 makeAdder의 환경에서 찾고, 그 다음으로 전역 Lexical 환경에서 찾는다.

내부 - 외부 - 전역의 순서를 이해하면 된다.

 

클로저(Closure)

- 위 설명을 통해 내부 - 외부 - 전역으로 이어지는 Lexical 환경과 관계를 이해했다면, 이제 클로저라는 개념을 이해할 수 있다.

- 앞에서 살펴보았듯, makeAdder 함수의 return 값으로 반환되는 익명 함수는 내부적으로 y를 갖고 있고,

내부적으로 갖고 있지 않은 x는 상위 함수인 makeAdder의 x에 접근하여 가져오게 된다.

- 그리고 makeAdder 함수를 사용해서 add3 함수가 생성된 이후에도 여전히 상위함수인 makeAdder의 x에 접근할 수 있다. 

- 이것을 클로저 (Closure) 라고 한다. 함수와 그 함수의 렉시컬 환경의 조합이다.

함수가 생성될 당시의 외부 변수를 기억하고, 생성된 이후에도 계속 접근이 가능한 기능이다.

외부 함수의 실행이 끝나서 외부 함수가 소멸됐다 하더라도, 내부 함수는 외부 함수의 변수에 계속해서 접근이 가능하다.

예시에서 봤던 것처럼, makeAdder 함수가 add3 함수를 생성하고 실행이 끝났음에도, add3 함수는 계속해서 자신의 외부 함수인 makeAdder 의 변수를 기억하고 접근할 수 있는 것이다. 

그렇기 때문에 add3(2) 에서 x+y 를 할 때, 함수 내부에 없는 x 값을 자신의 상위 함수인 makeAdder의 x에서 가져오게 된다.

- 또한 생성 당시의 변수를 기억하는 것이기 때문에, add3 과 add10은 makeAdder라는 같은 함수를 사용해서 생성했음에도 불구하고 서로 다른 환경을 갖게 된다.

즉, add3는 makeAdder(3)으로 x는 3인 환경을 만들어준 것이다.

add3(y) 에서 y 에 무슨 값을 넣든 x는 add3 생성 당시의 x 값인 3이므로, 3을 더하는 환경을 만들어준 것이다.

반면, add10 은 makeAdder(10)으로 x는 10인 환경을 만들어준 것으로 add3과는 다른 환경이다.

add10 생성 당시의 x 값인 10으로, add10(y)에서 y에 무슨 값을 넣든 10을 더하는 환경을 만들어준 것이다.

 

counter 함수와 closure 

- makeCounter로 1씩 증가하는 counter라는 함수를 생성해주고 나면,

num의 시작점을 바꾼다거나 갑자기 num 을 99나 100 등의 다른 숫자로 바꿀 수 없다.

counter 함수를 생성할 당시 num = 0 이고, 이 값은 바꿀 수 없다.

주어진 num을 기억하고 거기에 1씩 더할 수 있을 뿐이다.

즉, num 이라는 변수의 은닉화에 성공한 것이다.

 

댓글