함수 내부에 함수를 작성할 때마다, 여러분은 클로저를 생성한 것입니다. 내부에 작성된 함수가 바로 클로저죠. 클로저는 차후에 외부 함수의 변수를 사용할 수 있기 때문에 대개 반환하여 사용합니다.
내부함수는 외부함수의 지역변수에 접근 할 수 있는데 외부함수의 실행이 끝나서 외부함수가 소멸된 이후에도 내부함수가 외부함수의 변수에 접근 할 수 있다.
이러한 메커니즘을 클로저라고 한다
언제나 그래 왔듯이 개념만 봐서는 이해가 안 될 것이다. 우선 아래 코드를 봐보자.
function outerFn() {
let outerVar = 'outer';
console.log(outerVar);
function innerFn() {
let innerVar = 'inner';
console.log(innerVar);
}
}
let globalVar = 'global';
outerFn();
다음 코드에서 innerFn() 함수에서 접근할 수 있는 Scope 는 총 몇 개일지 생각해보고 스크롤을 내려보자.
function outerFn() {
let outerVar = 'outer';
console.log(outerVar);
function innerFn() {
let innerVar = 'inner';
console.log(innerVar);
}
}
function innerFn() {
let innerVar = 'inner';
console.log(innerVar);
}
function outerFn() {
let outerVar = 'outer';
console.log(outerVar);
function innerFn() {
let innerVar = 'inner';
console.log(innerVar);
}
}
let globalVar = 'global';
outerFn();
위와 같이 저 코드에선 총 3개의 Scope 가 존재한다.
(다시 상기하자. Scope는 범위를 뜻한다.)
이해가 안됐다면 Scope 를 다시 공부하고 와야한다. 이해가 됐다면 아래 코드를 봐보자.
function outerFn() {
let outerVar = 'outer';
console.log(outerVar);
function innerFn() {
let innerVar = 'inner';
console.log(innerVar);
}
return innerFn;
}
outerFn(); // ?
위 코드에서 outerFn() 의 값은 무엇일까? 고민해본 후 스크롤을 내리길 바란다.
우선 outerFn() 을 실행시키면 함수 외부에 있는 console.log(outerVar);
을 통해 'outer' 를 출력할 것이고 그 다음 return innerFn;
을 통해 함수 innerFn()
자체를 리턴하게 될 것이다. 따라서 결과는 아래와 같다.
function outerFn() {
let outerVar = 'outer';
console.log(outerVar);
function innerFn() {
let innerVar = 'inner';
console.log(innerVar);
}
return innerFn;
}
outerFn(); //
'outer', function innerFn() {
let innerVar = 'inner';
console.log(innerVar);
}
여기서 다음과 같은 의문이 생길 수 있다.
"아니 분명 함수 외부쪽에 return innerFn;
을 해줬는데 왜 'inner' 가 출력이 안되고 함수 그 자체가 출력되는거죠?"
저 문제에는 사실 함정이 있다. return innerFn;
을 잘 봐보면 리턴 값으로 innerFn
이라는 변수를 준 모습이다. 그렇기 때문에 innerFn
변수가 가지고 있는 함수식 그 자체가 리턴 값이 된 것이다. 만약, return innerFn()
처럼 리턴 값으로 innerFn()
이라는 함수를 줬으면 여러분이 원하는대로 결과는 'outer', 'inner'
가 출력 될 것이다.
이해가 됐다면 아래 코드를 봐보자.
function outerFn() {
let outerVar = 'outer';
console.log(outerVar);
function innerFn() {
let innerVar = 'inner';
console.log(innerVar);
}
return innerFn;
}
outerFn()(); // ?
let innerFn = outerFn(); // ?
innerFn(); // ?
위 코드에서 묻는 3가지 질문에 대한 답은 무엇일까? 곰곰히 생각해보고 스크롤을 내리길 바란다.
outerFn()();
부터 살펴보자. 뜬끔없이 () 괄호가 두 번 나와서 당황 했을 수도 있다. 의미만 알면 별 거 없으니 괜찮다.
outerFn();
의 경우에는 어떤 값을 출력했는가? 위에서 봤듯이 외부 함수의 값인 'outer'
는 제대로 출력했지만 innerFn()
은 함수 자체를 리턴한 것을 확인할 수 있었다.
그렇다면 outerFn()();
무슨 의미인지 대충 짐작이 가지 않는가?
괄호를 두 번 써주는 것의 의미는 바로 outerFn()
함수의 내부 함수까지 출력하라는 뜻이다.
따라서 결과는 아래와 같다.
outerFn()(); // 'outer','inner'
만약 내부 함수가 2개라면 outerFn()()();
를 해줘야 하는 것이다.
이해가 됐다면 두 번째를 봐보자.
let innerFn = outerFn(); // ?
을 보면 innerFn
이라는 변수에 outerFn()
함수 값을 넣어준 것을 볼 수 있다. 위 코드에서 let innerFn = outerFn();
은 전역 스코프에서 선언된 변수이고 따라서 내부 함수의 return innerFn;
에 영향을 줄 수 있다.
결과는 아래와 같다.
let innerFn = outerFn(); // 'outer'
이해가 됐다면 세 번재를 봐보자.
innerFn();
은 무엇을 호출할까? 솔직히 너무 쉽다. 그냥 innerFn()
을 호출한다.
따라서 결과는 innerFn(); // 'inner'
이다.
그렇다면 우리는 위 내용을 다음과 같이 정리할 수 있다.
클로저는 외부 함수의 변수에 접근할 수 있는 내부 함수를 뜻한다.
아래 코드를 봐보자.
function outerFn() {
let outerVar = 'outer';
console.log(outerVar);
function innerFn() {
let innerVar = 'inner';
console.log(innerVar);
}
return innerFn;
}
let globalVar = 'global';
let innerFn = outerFn();
innerFn();
위 코드에서 클로저 함수는 어떤걸까?
function innerFn() {
let innerVar = 'inner';
console.log(innerVar);
}
내부 함수가 클로저 함수이므로 위 코드가 클로저 함수다.
클로저 함수의 접근 가능 범위는 아래와 같다.
이렇게 새롭게 정의된 스코프는 상위의 스코프에 접근할 수 있고 가장 내부에서 바깥쪽으로 접근 해 나가는 방식을 Scope Chain (스코프 체인) 이라고 한다.
다시 한 번 말하지만, 클로저 함수는 변수에 접근이 가능한 것이다.
Curring (커링) 이란 함수 하나가 n개의 인자를 받는 대신, 따로 n개의 함수를 만들어 각각 인자를 받게 하는 방법이다.
역시 개념만 봐서는 잘 모르겠으니 아래를 먼저 봐보자.
function greet(greeting, name){
console.log(greeting + , ", " + name);
}
위의 함수가 동작되기 위해선 인자 2개를 받아야 한다.
이 함수를 커링 방법을 이용해 바꿔보자.
function greet(greeting){
return function(name){
console.log(greeting + ", " + name);
}
}
첫번째 함수가 greeting 인자를 받고, 반환되는 내부 함수가 name 인자를 받는 모습이다.
여기까지만 보면 이런 의문을 가질 수도 있다.
"아니 첫 방법이 간단하고 좋은데 뭣하러 커링 방법을 써서 저렇게 코드를 복잡하게 쓰는거야?"
이에 대한 답변은 아래를 보면 알 수 있다.
let hello = greet("hello");
hello("world"); // 'hello, world'
hello("Yongho"); // 'hello, Yongho'
위를 보면 변수 hello
에 greet("hello")
값을 할당했다. (hello 라는 단어를 고정값으로 두려는 것이다.)
이후 변수 hello의 인자값(name) 으로 "world" 와 "Yongho" 를 받게되면
return function(name){
console.log(greeting + ", " + name);
}
을 통해 위와 같이 'hello, world'
, 'hello, Yongho'
를 출력하게 되는 것이다.
따라서 커링을 사용하는 이유는 가장 먼저 받는 인자를 고정값을 목적으로 사용하고 나중에 받는 인자는 유동적인 값을 받아 두 가지 함수를 하나의 함수인 것처럼 사용하는 것에 목적이 있다고 할 수 있다.
아래는 어느 블로그의 커링에 대한 설명을 인용한 것이다.
주의 할게 있다면 커링 함수에서는 인자의 순서가 매우 중요하다.
외부 함수 즉 가장 먼저 받는 인자일 수록 변하지 않아야 하고, 내부 함수 즉 가장 나중에 받는 인자일 수록 변할 가능성이 높다.
이런 것들을 생각해가며 설계를 해야한다.