Closure (클로저)
함수가 자신이 선언됐을 때의 환경인 스코프를 기억하여 자신이 선언됐을 때의 환경 밖에서 호출되어도 그 환경에 접근할 수 있는 함수를 뜻한다. (함수가 실행되는 위치가 어디인지는 관계가 없다. )
렉시컬 스코프
스코프는 함수를 호출할 때가 아니라 함수를 어디에 선언하였는지에 따라 결정이 된다. 이를 렉시컬 스코핑이라고 한다.
var x = 1;
function foo() {
var x = 10;
bar();
}
function bar() {
console.log(x);
}
foo(); // 1
bar(); // 1
언뜻보면 foo에서 실행이 되어 x가 foo 내부의 x를 가리킬 것 같지만 실제 bar가 선언됐을 당시의 x인 1을 가리키게 된다. 이처럼 렉시컬 스코프는 함수를 어디서 호출하는지가 아니라 어디에 선언하였는지에 따라 결정된다.
Closure Example #1
function say (){
var a = 2;
var b = 3;
function log () {
console.log(a + b);
}
return log;
}
var a = say();
a();
코드가 실행되는 순서
- say라는 함수 생성
- a라는 변수 선언
- say라는 함수의 실행값을 a에 담고있다.
- say라는 함수의 실행값을 담으려면 say가 실행되어야 하기 때문에 say라는 함수가 실행이 된다.
- say라는 함수가 실행되게 되면 전체 global scope안에서 say라는 scope가 생기게 된다.
- say라는 scope에는 a = 2, b = 2이런 정보들이 저장이 됨
- log라는 함수가 있다라는 것까지 인식을 하게 됨 ( 변수를 선언하는 것과 사실상 동일)
- log라는 것을 return을 한다.
- global의 a에 log라는 함수를 할당을 하게 된다.
- global scope에서 a라는 함수를 실행한다.
- 실행을 하게 되면 log라는 함수 내부 코드가 실행이 된다.
- log라는 함수도 scope 정보가 생성이 된다.
- log라는 함수 스코프 내부에는 별다른 변수선언이 없기 때문에 콘솔을 바로 실행
- a + b 에서 a와 b가 뭔지 찾는다.
- say라는 함수의 스코프와 log라는 함수의 스코프는 서로 접근할 수 있는 관계가 아니지만 선언할 당시, log라는 함수는 say라는 함수 내부에서 생성이 되었기 때문에 say라는 함수의 내부 환경을 기억하게 되어 a를 기억해낸다. b또한 마찬가지로 본인이 생성된 환경의 주변의 정보인 b의값을 기억해낸다.
log라는 함수가 선언된 환경은 현재 say라는 함수 내부이다.
해당 위치에서 생성이 되었고 선언이 되었기 때문에
해당 위치에서 접근할 수 있는 해당 위치를 기준으로 변수같은 것을 접근할 수 있는것이다. 이런것을 클로저함수라 한다.
Closure Example #2
function doSomething () {
var a = 1;
function something() {
console.log(a);
}
foo (something);
};
function foo (fn) {
fn();
};
doSomething();
코드가 실행되는 순서
- doSomething이라는 함수를 선언
- foo라는 함수 선언
- doSomething이라는 함수 실행
- doSomething이라는 함수를 실행하게 되면 Global 스코프안에 doSomething스코프 정보가 저장이 된다.
- a = 1라는 정보 저장, something이라는 함수 만듦, 2번에 선언했던 foo라는 함수 실행
- foo라는 함수는 doSomething 스코프 밖이지만 내부에서 외부로는 접근이 가능하기 때문에 foo라는 함수에 접근해서 사용할 수 있어서 something이라는 함수를 인자값으로 넣고 실행
- 실행이 되게 되면 foo라는 함수의 스코프 정보도 컴퓨터 내부에 생성이 되게 되는데 foo라는 스코프 정보가 생긴다.
- foo라는 함수는 fn이라는 매개변수가 선언이 되어있다. 이 매개변수 또한 foo라는 함수 스코프 내부에 새롭게 생기는 것으로 fn = something이다 이런식으로 생김
- foo라는 함수는 이제 더이상 변수라는 것이 없기 때문에 fn()코드를 바로 실행한다. fn을 실행한다는 것 = something을 실행한다.
- something이라는 함수는 콘솔 a를 하게 된다.
( 선언과 실행의 순서로만 따졌을때는 실제로 something은 doSomething스코프 밖의 foo라는 함수에서 실행이 되었기 때문에 a에 접근이 불가능하다. )
- something을 foo라는 함수 내부에서 실행을 했고 doSomething과 foo는 서로의 스코프에 접근할 수 없는 관계이다.
하지만 모든 함수는 본인이 선언된, 생성된 주변 환경의 정보를 기억할 수 있게 되기 때문에 something이 접근할 수 있는 a의 정보를 기억한 상태로 찾아서 실행이 된 것이다.
함수가 본인이 생성된 주변 환경 정보를 지속적으로 기억할 수 있는 것이 클로저의 가장 큰 특징이라고 할 수 있다.
Closure Example #3
var foo;
function doSomething(){
var a = 1;
function something(){
a++;
console.log(a);
}
foo = something;
};
function say(){
foo();
};
doSomething();
say();
say();
코드가 실행되는 순서
- foo라는 변수 선언 ( 이때, foo = undefined의 값을 갖게됨)
- doSomething 함수를 생성
- say라는 함수 생성
- doSomething 함수 실행
- doSomething이라는 함수의 스코프 정보가 생김
- a = 1 이라는 정보가 스코프 내에 생기게 됨
- something이라는 함수 선언, 스코프 생성
- foo라는 변수에 something할당
- something이라는 함수에 주소값이 생성, 변수 foo에 함수의 주소를 할당
- say라는 함수 실행 , 스코프 정보가 생김
- foo라는 함수 접근
- foo라는 함수는 something이라는 함수와 동일 사실상 something을 실행
- 선언과 실행 부분을 보게되면 say라는 스코프 내에서 foo라는 함수를 실행시키고 a를 증가시킨다. 원래는 a를 찾지 못하여야 하지만 something이라는 함수의 생성당시 주변의 정보를 기억하여 a를 찾아낸 후 증가시킨다. (something이라는 함수의 생성당시를 중심으로)
- say라는 함수 한번 더 실행
- say라는 함수가 한번 더 실행이 되게 되면 기존에 있던 스코프정보가 있지만 다시 스코프 정보를 별도로 만들게 되고 foo라는 함수를 실행시킨다.
- 아까전의 say함수를 실행했기 때문에 a는 1에서 2로 변한상태이다.
something이라는 함수가 생성될 당시에는 a가 1이었고 나중에 2로 변경되었다. 하지만 클로저의 특징은 나중에 바뀐 값까지 다 알고 있게 된다. (1에서 2로 바뀌었는지, 2에서 3으로 바뀌었는지 등..) 본인이 기억하고 있는 주변 상황에 대한 정보를 지속적으로 쳐다보고 있다고 생각하면 된다.
- 그래서 좀전의 2에서 3으로 증가시키게 된다.
클로저는 함수가 선언되고 생성되고 생성된 위치를 기준으로 생각하는게 가장 쉽다.
생성된 위치를 기준으로 거기서 접근이 가능한 변수들은 계속 접근이 가능하며
함수가 실행되는 위치는 사실상 상관이 없다.
함수가 실행되는 위치에서는 접근할 수 없는 변수지만 클로저로 인해 사실상 함수의 선언부분의 위치에서 실행이되는것이기 때문에 접근할 수 있게된다.
Closure Example #4
function makeAdder(x) {
return function add(y){
return x + y;
};
};
var addFive = makeAdder(5);
var addTen = makeAdder(10);
addTen(2);
addFive(1);
코드가 실행되는 순서
- makeAdder라는 함수 선언 (실행하지는 않음)
- addFive라는 변수를 선언하고 makeAdder라는 함수를 실행한 결과값을 담는다.
- 함수가 실행되면 스코프가 생기게 되며 1번 단계에서 생성했던 makeAdder라는 함수의 스코프 생성, addFive라는 변수 생성
- 5라는 매개변수를 넘겨줌 , MakeAdder라는 함수의 스코프 내부에 x = 5라는 정보가 생기게 됨
- 함수를 만들어서 return을 하고 있기 때문에 make Adder라는 함수에는 add라는 함수에는 만들어지고 그 함수가 return되게 된다.(add라는 함수가 만들어질때의 주소값을 '213'이라고 했을경우, 그 주소값을 return을 하게 된다.)
- addFive에 담기는 것도 '213'이라는 그 주소값이 담기게 된다.
- addTen이라는 변수를 만들고 여기에도 makeAdder라는 함수를 실행한 결과값을 담는다.
- makeAdder라는 스코프 정보가 또다시 새롭게 만들어진다. ( = 좀 전것과는 별개로 스코프가 생성이 된다. )
- 인자로 10을 넣어주고 있기 때문에 두번째로 만들어진 makeAdder의 스코프에는 x = 10. 이라는 정보가 스코프 내부에 저장이 된다.
- add라는 함수를 만들어서 return을 한다. (function이라는 키워드 사용은 함수를 만든다는 것)
- add라는 함수의 정보가 스코프 내부에 새롭게 생긴다. (함수가 생긴다는 것은 참조값이 생긴다고 볼 수 있다, 이때 만들어지는 add라는 함수의 주소값을 '456'이라고 가정할 경우, '456'이라는 주소값이 add라는 함수에 할당이 되었다.)
- makeAdder가 첫번째로 실행됐을때의 만들어진 add라는 함수와 makeAdder가 두번째로 실행됐을때의 만들어진 add라는 함수는 별개이므로 addTen에는 '456'이라는 주소값이 담기게 된다.
- addTen을 실행한다. ('456'에 위치한 add라는 함수를 실행하게 된다.)
- '456'의 add라는 함수의 스코프가 생기게 되고 y이라는 정보를 가지고 있으며 매개변수 2가 있기 때문에 스코프 내부에 y = 2라는 정보를 저장하게 됨
- x + y를 해서 return을 하게 되는데 이때, x의 값이 무엇인지 살펴보았을때 '456'의 add가 생성된 위치의 스코프를 봐야한다. 해당 add가 생성된 스코프는 두번째로 실행된 makeAdder의 스코프 내부이기 때문에 이 경우의 x 는 10이 된다.
- 따라서 addTen의 결과는 12가 된다.
- 마지막으로 addFive를 실행하게 된다.
- '213'이라는 주소의 add의 스코프가 만들어지며 15단계~ 18단계를 진행한다. (이때 '213' 이라는 주소의 add는 makeAdder의 첫번째로 실행됐을 때 만들어졌기 때문에 이때의 x는 5가 된다.
함수가 생성되는 위치 기준으로 생각을 하고 함수가 생성된다는 것이 뭔지 (function이라는 키워드를 쓴다는 것이 함수가 생성된다는것), 함수가 생성된다는 것을 자세히 보는것이 중요하다.
참고 사이트