JavaScript는 렉시컬 환경을 갖는다. 다음가 같은 코드가 있다. 위에서부터 아래로 어떻게 동작하는지 살펴보자.
코드가 실행되면 스크립트 내에서 선언한 변수들이 렉시컬 환경에 올라간다. 참고로 let으로 선언된 변수도 호이스팅된다. 렉시컬 환경에 올라가지만 초기화가 안 되어 있을 뿐이다. 그래서 사용은 불가능하다. 그에 비해 함수 선언문은 변수와 달리 바로 초기화된다. 그래서 저 위치에서도 사용 가능하다. 변수에 할당한 함수 표현식은 저렇게 안 된다.
처음으로 one이라는 변수가 선언된다. 아직 할당은 되어 있지 않기 때문에 초기값 undefined를 갖는다. 이제 사용해도 에러는 발생하지 않는다. 값이 undefined일 뿐이다.
다음으로 one에 숫자 1이 할당된다. 한편 함수 선언은 초기에 이미 완료되었고 이제 마지막 라인으로 가서 함수가 실행된다. 그 순간 새로운 렉시컬 환경이 만들어진다. 이곳에는 함수가 넘겨받은 매개변수와 지역변수가 저장된다.
함수가 호출되는 동안 함수에서 만들어진 내부 렉시컬 환경과 외부에서 받은 전역 렉시컬 환경 두 개를 가진다.
내부 렉시컬 환경은 외부 렉시컬 환경에 대한 참조를 갖는다. 지금은 저 함수의 외부 렉시컬 환경이 전역 렉시컬 함수이다. 코드에서 변수를 찾을 때 내부에서 찾고 없으면 외부, 거기에도 없으면 전역 렉시컬 환경까지 범위를 넓혀 찾는다. 이 코드에서 저 one과 num은 내부 렉시컬 함수에서 우선 찾는다. 그런데 num은 찾았지만 one이 없다. 그러면 외부로 범위를 넓혀서 있는지 찾게 된다. 그렇게 찾고 나면 더해줄 수 있는 것이다.
한 가지 예를 더 살펴보자. add 함수를 만들어주는 함수 makeAdder이다.
최초 실행시 makeAdder함수와 변수 add3은 전역 렉시컬 환경에 들어간다. add3은 초기화가 안 된 상태이고 사용할 수 없다.
이 라인이 실행될 때 makeAdder가 실행되고 그러면서 렉시컬 환경이 만들어진다. 여기에서 전달받은 x의 값이 들어간다. 함수의 렉시컬 환경에는 넘겨받은 매개변수가 지역변수가 저장된다.
전역 렉시컬 환경에 있던 add3은 이 함수가 실행됐으니 return하는 함수가 된다.
마지막 줄을 실행한다. add3을 실행하면 저 함수가 실행되는데, 이때 또 렉시컬 환경이 만들어진다. 이번에는 y가 2로 들어간다.
이제 x + y를 해보자. (1) 처음에는 여기에서 x와 y를 찾는다. y는 있는데 x가 없으니 (2) 참조하는 외부 렉시컬 환경으로 가서 x를 찾았다.
정리해보자. 이 함수는 자신이 y를 가지고 있고 상위함수인 makeAdder의 매개변수 x에 접근할 수 있다.
add3 함수가 생성된 이후에도 변함없이 상위함수로 호출할 때 사용했던 인수에 접근 가능하다. 이런 것을 클로저라고 한다. 클로저는 함수와 렉시컬 환경의 조합이다. 함수가 생성될 당시의 외부 변수를 기억하고, 생성된 이후에도 그 변수에 계속 접근 가능한 기능인 것이다. 외부함수의 실행이 끝나서 외부함수가 소멸돼도 내부함수가 외부함수의 변수에 접근할 수 있다.
여기에 보면 makeAdder(10)이 호출되지만 add3()에는 변화가 없다. add10()과 add3()은 서로 다른 환경을 갖고 있는 것이다.
counter에 makeCounter()가 리턴하는 함수, 즉 이 함수를 넣었다. 이 함수는 숫자를 반환하는데, 외부 함수의 변수이다. 실행하면 초기값 0, 계속하면 1씩 증가한다. 내부함수에서 외부함수의 변수, 그러니까 num에 접근하는 것이다. 이렇게 생성된 이후에 계속 기억하고 있는 것이다.
이 숫자들을 변경할 수 있을까? 불가능하다. 오직 counter를 증가시키고 반환할 뿐이다. 은닉화에 성공한 것이다. 갑자기 99로 바꾼다거나 100씩 증가시킬 수 없다.
출처: 코딩앙마, 자바스크립트 중급 강좌 #11 클로저(Closure) 5분만에 이해하기