자바스크립트는 어휘적 환경(Lexical Environment)을 갖는다.
어휘적 환경이란...?
=> 간단하게 생각하면, 정적인(변하지 않는) 환경이다!
let one;
one = 1;
function addOne(num){
console.log(one + num);
}
addOne(5);
이러한 코드가 있을 때 어떻게 동작하는 지 알아보자!
코드가 실행되면 스크립트에서 선언한 변수들이 Lexical 환경에 올라가게 된다. let으로 선언된 변수도 호이스팅 되어지고, Lexical 환경으로 올라가지만 초기화가 되어있지 않은 상태이다. 초기화가 되어있지 않기 때문에 사용은 못한다. 그에 비해 함수 선언문은 변수와 달리 바로 초기화 되어진다. 저 위치에서도 사용이 가능하다. 변수에 할당한 함수 표현식은 저러한 형식으로는 되지 않는다.
이제 let one
을 만나게 되면 아직 할당은 되어있지 않기 때문에 초기값 undefined를 얻게 된다. 이제 사용을 해도 에러가 발생하지 않는다. 값이 undefined일 뿐이다.
이제 one에 숫자 1이 할당되어졌다.
함수 선언은 초기에 완료되었기에 마지막 라인으로 가서 함수가 실행되어진다. 그 순간 새로운 Lexical 환경이 만들어지게 된다. 이곳에는 함수가 넘겨받은 매개변수와 지역변수들이 저장되어진다. 함수가 호출되는 동안 함수에서 만들어진 내부 Lexical 환경과 외부에서 받은 전역 Lexical 환경 두 개를 가지게 된다. 내부 Lexical 환경은 외부 Lexical 환경에 대한 참조를 가지게 된다. 지금은 저 함수의 내부 Lexical 환경이 전역 Lexical 환경이다. 즉 코드에서 변수를 찾을 때 내부에서 찾고 없으면 외부, 거기에도 없다면 전역 Lexical 환경까지 범위를 넓혀서 찾는다.
이 코드에서 one과 num은 내부 Lexical 환경에서 먼저 찾는다. num은 찾았지만 one은 없기에 이러한 경우 외부로 넓혀서 있는지 찾고 난 후 찾게 되면 비로소 식을one + num
실행할 수 있게 된다.
Add함수를 만들어주는 함수 makeAdder
최초 실행 시 makeAdder
와 변수 add3
은 전역 Lexical 환경에 들어가게 된다. add3
은 초기화가 되지 않은 상태이고, 사용할 수 없다.
노란색 라인이 실행될 때 makeAdder
가 실행되고, Lexical 환경이 만들어지게 된다. 이 때 전달받은 x의 값이 들어가게 된다. 함수의 Lexical 환경에는 넘겨받은 매개변수와 지역변수들이 저장되어진다.
전역 Lexical 환경에 있던 add3
은 함수가 실행되어서 return 하는 함수가 되어진다.
이제 마지막 줄이 실행되어지면 빨간 줄의 함수가 실행되어진다. 이 때 또다른 Lexical 환경이 만들어지게 된다. y가 2로 들어가게 된다.
이제 x + y
를 하게 된다면
1) x와 y를 찾게 된다. y는 있지만
2) x는 없으므로 참조하는 외부 Lexical 환경으로 가서 찾게 된다.
정리를 하면 이 함수는 자신이 y를 가지고 있고, 상위 함수인 makeAdder의 x에 접근할 수 있다.
add3함수가 생성된 이후에는 상위함수인 makeAdder의 x에 접근이 가능하다. 이것을 클로저라고 한다!!!!
Closure
- 함수와 그 함수의 렉시컬 환경의 조합
- 함수가 생성될 당시의 외부 변수를 기억하고
- 생성된 이후에도 계속해서 접근이 가능하다
외부 함수의 실행이 끝나서 외부 함수가 소멸이 되어도 내부 함수가 외부함수에 접근할 수 있다.
여기서 makeAdder(10)
이 호출되어지지만 add3
은 아무런 변화가 없다. 즉 add10
과 add3
은 서로 다른 환경을 가지고 있는 것이다.
위의 예제를 봐도 잘 이해가 되지 않아서 다른 예제들을 가지고 왔다.
function outer(){
const a = 1;
console.log(a); // 1
}
outer();
여기서 outer는 아래와 같은 Lexical 환경을 가지게 된다. a라는 변수는 1이 들어있다는 표를 가지게 된다.
outer
A | 1 |
---|
function outer(){
const a = 1;
const b = 'B';
function inner(){
const c = 'hi';
conosole.log(b); // B
}
inner();
}
outer();
inner
c | hi |
---|
outer
A | 1 |
---|---|
b | B |
해당 콘솔에 어떻게 b가 찍히게 되었을까?라는 의문을 가지게 된다. inner안에는 분명히 b가 없는데 말이다! inner()함수가 outer{}에 들어가있기 때문에 영향을 미치게 된다. 즉, 해당 블록{}
안에서 사용이 가능하다. 이 때 inner안에는 b가 없으므로 해당 값을 찾기 위에서 outer까지 범위를 넓혀서 찾게 된다.
이제 총 정리를 해보면
클로저는 내부함수의 변수가 외부함수의 변수에 접근할 수 있는 것을 의미한다. 즉 클로저 함수 안에서는 지역변수, 외부함수의 변수, 전역변수 모두 다 접근이 가능하다. 클로저는 변경이 되지 않아야 하지만 해당 값을 읽고 싶을 때 주로 사용되어진다
출처)