TIL 2020-10-22 (Closure)

nyongho·2020년 10월 21일
1

JavaScript

목록 보기
6/23
post-thumbnail

Level 2-2 Closure


TIL LIST

  • Closure

1) Closure 이해하기

함수 내부에 함수를 작성할 때마다, 여러분은 클로저를 생성한 것입니다. 내부에 작성된 함수가 바로 클로저죠. 클로저는 차후에 외부 함수의 변수를 사용할 수 있기 때문에 대개 반환하여 사용합니다.

내부함수는 외부함수의 지역변수에 접근 할 수 있는데 외부함수의 실행이 끝나서 외부함수가 소멸된 이후에도 내부함수가 외부함수의 변수에 접근 할 수 있다.
이러한 메커니즘을 클로저라고 한다

언제나 그래 왔듯이 개념만 봐서는 이해가 안 될 것이다. 우선 아래 코드를 봐보자.

1. Scope 개념 확립

function outerFn() {
 let outerVar = 'outer';
 console.log(outerVar);
 
 function innerFn() {
 let innerVar = 'inner';
 console.log(innerVar);
 }
}

let globalVar = 'global';
outerFn();

다음 코드에서 innerFn() 함수에서 접근할 수 있는 Scope 는 총 몇 개일지 생각해보고 스크롤을 내려보자.

  1. outerFn(), innerFn() 을 합친 Scope
function outerFn() {
 let outerVar = 'outer';
 console.log(outerVar);
 
 function innerFn() {
 let innerVar = 'inner';
 console.log(innerVar);
 }
}
  1. innerFn() 자신의 Scope
 function innerFn() {
 let innerVar = 'inner';
 console.log(innerVar);
 }
  1. 1번 2번을 합치고 Global Scope에 선언된 변수 및 outerFn() 을 합친 모든 Scope
function outerFn() {
 let outerVar = 'outer';
 console.log(outerVar);
 
 function innerFn() {
 let innerVar = 'inner';
 console.log(innerVar);
 }
}

let globalVar = 'global';
outerFn();

위와 같이 저 코드에선 총 3개의 Scope 가 존재한다.
(다시 상기하자. Scope는 범위를 뜻한다.)

이해가 안됐다면 Scope 를 다시 공부하고 와야한다. 이해가 됐다면 아래 코드를 봐보자.

2. Closure 맛보기

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' 가 출력 될 것이다.

이해가 됐다면 아래 코드를 봐보자.

3. Closure 활용 해보기

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' 이다.

그렇다면 우리는 위 내용을 다음과 같이 정리할 수 있다.


2) Closure 의 정의

클로저는 외부 함수의 변수에 접근할 수 있는 내부 함수를 뜻한다.

아래 코드를 봐보자.

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);
 }

내부 함수가 클로저 함수이므로 위 코드가 클로저 함수다.

클로저 함수의 접근 가능 범위는 아래와 같다.

클로저 함수의 접근 가능 범위 3가지

1. 지역 변수 (innerVar)

2. 외부 함수의 변수 (outerVar)

3. 전역 변수(globalVar)

이렇게 새롭게 정의된 스코프는 상위의 스코프에 접근할 수 있고 가장 내부에서 바깥쪽으로 접근 해 나가는 방식을 Scope Chain (스코프 체인) 이라고 한다.

다시 한 번 말하지만, 클로저 함수는 변수에 접근이 가능한 것이다.


3) 유용한 Closure 예제

커링의 개념 및 활용

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'

위를 보면 변수 hellogreet("hello") 값을 할당했다. (hello 라는 단어를 고정값으로 두려는 것이다.)

이후 변수 hello의 인자값(name) 으로 "world" 와 "Yongho" 를 받게되면

return function(name){
        console.log(greeting + ", " + name);
    }

을 통해 위와 같이 'hello, world' , 'hello, Yongho' 를 출력하게 되는 것이다.

따라서 커링을 사용하는 이유는 가장 먼저 받는 인자를 고정값을 목적으로 사용하고 나중에 받는 인자는 유동적인 값을 받아 두 가지 함수를 하나의 함수인 것처럼 사용하는 것에 목적이 있다고 할 수 있다.

커링 사용시 주의 사항

아래는 어느 블로그의 커링에 대한 설명을 인용한 것이다.

주의 할게 있다면 커링 함수에서는 인자의 순서가 매우 중요하다.
외부 함수 즉 가장 먼저 받는 인자일 수록 변하지 않아야 하고, 내부 함수 즉 가장 나중에 받는 인자일 수록 변할 가능성이 높다.
이런 것들을 생각해가며 설계를 해야한다.

profile
두 줄 소개

0개의 댓글