Frontend/JavaScript

클로저

findmypiece 2021. 3. 9. 16:07
728x90

나는 백엔드 개발자다.


최근에는 프론트엔드에도 많은 관심을 가지고 학습을 이어가고 있지만

정작 자바스크립트에 흥미를 느꼈던 시기에는 SI업체에 있었기 때문에

주어진 백엔드 작업을 쳐내는 것도 바빴고 게임에도 빠져 있어서

출퇴근 길에 구글링으로 지적호기심을 채우는 것이 고작이었다.

 

그때가 약 5년 전 이었던 것 같은데 Node가 등장하고

지금의 라인플러스에서 자바스크립트 고급 개발자를 공격적으로 모집하던 시기였던 것 같다.

자바스크립트의 발전을 넋놓고 지켜볼 수 밖에 없었다.

 

아무튼 지금에 와서 리액트를 학습하며 자바스크립트를 다시 들여다보게 되었고

과거 완전히 이해하지 못하고 넘어갔던 클로저에 대해 확실히 정리를 해두려 한다.

 

클로저가 대체 뭘까?

자바스크립트의 특정 함수를 가리키는 건가? 아니면 특정 클래스를 가리키는 건가?

클로저는 실행이 종료된 구문 혹은 함수의 지역변수를

아직 실행되지 않은 함수 안에서 참조하는 상황에서 발생할 수 있는 것으로

함수 내부에서 실행이 종료된 외부 변수를 참조하는 매커니즘 자체를 의미한다.

이게 무슨 말인가?

 

기본적으로 자바스크립트에서는 아래와 같이 함수 안에 함수를 정의해서 사용할수 있다.

var outer = () => {
    var name = '이름이요';
    var inner = () => {
        alert(name);
    }
    inner();
}

outer();

이것을 실행하면 "이름이요" 라는 alert 창이 뜨는 것을 확인할 수 있다.

그런데 따지고 보면 name는 outer 안에서 전역변수이고

지역함수인 inner에서 해당 변수에 접근하는 것은 너무도 당연해 보인다.

 

그렇다면 이건 어떨까?

var outer = () => {
    var name = '이름이요';
    var inner = () => {
        alert(name);
    }
    return inner;
}

var outerIns = outer();
outerIns();

outer 함수에서 내부에서 선언한 inner 함수를 바로 실행하지 않고

함수 자체를 리턴하고 그 함수를 별도 변수에 담아서 실행했다.

결과는 역시 "이름이요" 라는 alert 창이 뜰 것이다.

 

대부분 클로저를 설명하는 글들을 보면 이게 부자연스럽다고 설명한다.

var outerIns = outer(); 단계에서 outer함수의 실행이 끝났기 때문에 

지역변수인 name는 메모리에서 소멸되어야 하고

alert 결과는 undefined가 떠야 자연스럽다는 것이다.

 

그도 그럴 것이 일반적인 프로그래밍 언어의 메모리 구조를 생각해보면

함수를 실행할때 지역변수 같은 것들은 스텍구조로 메모리에 push 했다가

실행이 종료되면서 pop하는 형태이다.

메모리에 계속해서 상주시킬 수는 없기 때문이다.

 

하지만 단순히 코딩을 하는 입장에서 보면 오히려 name 값이 유지되는 것이 더 자연스러워 보인다.

자바에서는 함수(메소드)에서 함수를 리턴하는 경우가 없기 때문에 

이런 고민이 필요없지만 자바스크립트는 위처럼 함수에서 함수를 리턴할 수도 있기 때문에

함수의 실행이 끝났더라도 아직 실행되지 않은 임의의 함수에 연결되어 있는 상태라면

이를 유지시켜줘야 했고 이 매커니즘 자체가 바로 클로저이다.

 

클로저 덕분에 보다 자연스러운 코딩이 가능해졌지만

문제는 클로저로 연결되어 있는 것은 지역변수의 값이 아니라 지역변수 자체이기 때문에

실제 함수를 실행할 당시의 지역변수 값을 읽어온다는 데에서 의도치 않은 문제가 발생하기도 한다.

 

아래를 보면 outer 에서 반복문을 통해 arr 배열에 지역변수 i 값을 리턴하는 함수를 선언해서 넣어주고 있다.

var outer = () => {
  var arr = [];
  for(var i = 0; i < 5; i++){
      arr[i] = () => {
      	return i;
      }
  }
  return arr;
}

var outerIns = outer();

for(var index in outerIns) {
    console.log(outerIns[index]());
}

 

var outerIns = outer(); 단계에서 outer 함수 실행은 종료되었지만

이에 arr 배열에 있는 아직 실행되지 않은 함수들에서

outer의 지역변수 i를 참조하고 있기 때문에 클로저가 생성된다.

 

이후 반복문을 통해 arr에 있는 함수를 실행시키지만

outer의 지역변수 i는 현재 5까지 증가한 상태이고

이를 구하는 함수를 참조하기 때문에 실행결과는 실행결과는 5가 5번 출력될 것이다.

그림으로 그려보면 아래와 같은 상태인 것이다.

 

어쨌든 목적은 arr에 함수를 담아야 하기 때문에

지역변수 i를 참조하는 함수의 실행이 완료된 결과를 얻어온 다음

해당 값을 리턴하는 함수를 만들어서 리턴하면 된다.

var outer = () => {
  var arr = [];
  for(var i = 0; i < 5; i++){
      arr[i] = ((param) => 
      	return () => {
        	return param;
      	}
      })(i);
  }
  return arr;
}

var outerIns = outer();

for(var index in outerIns) {
    console.log(outerIns[index]());
}

 

이렇게 되면 최종적으로 리턴되는 함수에 param 과의 클로저가 생성되긴 하지만

param은 지역변수 i를 참조하는 함수의 실행이 완료된 결과를 참조하고 있기 때문에

우리가 원하는 결과를 출력할 수 있다.

다시 그림으로 그려보면 아래와 같은 상태가 된 것이다.

728x90

'Frontend > JavaScript' 카테고리의 다른 글

ES6 비구조화 할당 문법  (0) 2021.03.10
클래스, 객체, 인스턴스  (0) 2021.03.09
script 태그 async, defer 옵션  (0) 2021.03.04
콜백함수와 비동기처리  (0) 2021.03.04
ES6 import, export  (0) 2021.03.03