본문 바로가기

Dev.FrontEnd/JavaScript

6. 자바스크립트의 클로저(Closure)에 대해서

Chapter 6. 자바스크립트의 클로저(Closure)

자바스크립트에는 없는 class역할을 대신해 비공개 속성/메소드, 공개 속성/메소드를 구현할 수 있는 방안을 마련한다.
따라서 캡슐화와 정보 은닉을 이해하기 위해 클로저를 알아야 한다.
아직 클로저가 무엇인지도 모르는데, 그렇다고 한다.

클로저
클로저는 두 개의 것(함수, 그 함수가 만들어진 환경)으로 이루어진 특별한 객체의 한 종류이다.
환경이라 함은 클로저가 생성될 때 그 범위 안에 있던 여러 지역 변수들로 이루어진다.
이 말 역시 무슨말인지 잘 모르겠다. 그렇다고 한다.

다음은 클로저가 생성되는 조건이다.
1) 내부 함수가 익명 함수로 되어 outer 함수의 반환값으로 사용된다.
2) inner 함수는 outer 함수의 실행 환경(execution environment)에서 실행된다.
3) inner 함수에서 사용되는 변수 x는 outer의 변수 스코프에 있다.
조건도 있는 것 같다. 그렇다고 한다.

역시 코드를 통해 알아봐야겠다.
code>
1
2
3
4
5
6
7
8
function outer( ){
  var name = "closure";
  function inner( ){
    alert(name);
  }
  inner( );
}
outer( ); // “closure"
cs

자바스크립트 함수의 변수스코프에서 변수 스코프 체인을 통해서
변수를 호출하기 위해 ‘여정’을 떠난다고 설명했다.
위 예제에서도 name이라는 변수를 호출하기 위해 내부함수, 외부함수, 그리고 전역 context로 여정을 떠나게 된다.
결과적으로는 외부함수 영역에 존재하는 name이라는 변수를 호출하게 되고
outer( )의 결과값은 “closure”가 된다.
그렇다면 다음 예제를 한 번 보자.
code>
1
2
3
4
5
6
7
8
9
10
function outer( ){
  var name=“closure”;
  function inner( ){
    alert(name);
  }
  return inner;
}
 
var callFunc = outer( );
callFunc( ); // “closure"
cs

결과부터 말하자면 위의 예제의 결과값은 첫번째 예제 코드의 결과값과 동일하다.
첫번째 예제와 다른점은 outer( )함수의 리턴값이 inner( )함수, 즉 내부함수를 리턴하고 있다.

이 때, callFunc( )함수를 클로저라고 부른다.

그렇다면 다음 예제는 어떻게 될까?
code>
1
2
3
4
5
6
7
8
9
10
function outer( ){
  var x = 0;
  return function( ){
    return ++x;
  };
}
 
var x = -1;
var f = outer( );
f( ); //  > 1
cs

클로저 f를 정의했다.
여기서의 문제는 익명으로 정의된 내부 함수에서
x++을 반환할 때, 이 x를 전역 변수 x 로 접근할 것인지, outer 함수의 x로 접근할 것인지이다.
결과부터 확인해보자.
1을 반환하는 것을 보니 outer 함수의 x 로 접근했다는 것을 알 수 있다.
즉, 런타임 변수는 렉시컬 환경을 기준으로 정의된 변수 스코프 및 체인에서 검색을 한다는 것이다.

이 때,
f( );를 한 번 더 호출하면
반환 값은 2, 3, 4 로 증가하게 된다.
이 말인 즉슨, f의 호출이 끝나고 나서도 그 부모의 변수 스코프에 있는 x의 값이 유지된다는 말이다.

내부 함수에서 선언된 변수가 아니면서
내부 함수에서 사용하는 outer의 x같은 변수를 자유변수(free variable)이라고 한다.

여기서 x 는 변수 스코프가 outer가 실행되는 환경으로까지 확장된다.
외부환경에서 내부 함수에 대한 참조 f를 갖고 있는 이상(즉, f가 메모리에서 사라지지 않는 이상)
outer 함수는 “실행 중” 상태가 된다.
따라서 자유변수 x 및 해당 변수 스코프 체인 관계는 메모리에서 계속 유지된다.

이처럼 outer 함수 호출이 종료되더라도
outer의 지역 변수 및 변수 스코프 객체의 체인 관계를 유지할 수 있는 구조를
클로저라고 한다.
정확히는 내부 함수를 가리키는 말이다.

핵심은 자유 변수이다.
자유 변수 값 자체는 렉시컬 환경의 영향을 받으면서
그 생명주기는 실행 환경의 영향을 받는다는 점이 클로저를 만드는 것이다.

한 가지 코드를 더 보자.
code>
1
2
3
4
5
6
7
8
9
10
11
function makeAdder(x){
  return function(y){
    return x + y;
  };
}
 
var add5 = makeAdder(5);
var add7 = makeAdder(7);
 
console.log(add5(3)); //  >  8
console.log(add7(6)); //  >  13
cs

이전 예제와 약간 다른 점이 외부함수에서 인자 x를 받고 내부함수에서 인자 y를 받는다.
makeAdder(x) 함수는 x라는 인자를 받아서 function(y){ return x+y } 를 만들어내는 함수라고 해석할 수 있다!
makerAdder(x) 함수를 통해 만들어진 두 함수 add5와 add7은
같은 정의를 갖지만 다른 환경, 즉 x값이 5인 환경과 x값이 7인 환경을 저장하는 것이다.


여기까지는 이론이었다.
이 복잡한 클로저 이론이 정말 실용적인가에 대한 의문을 가질 시간이다.
어떤 상황에서 클로저를 사용해야 하는가

클로저를 인스턴스를 생성하는 단위로 보자.
클로저란 것은 내부 함수를 반환값으로 사용하는 특수한 함수로 볼 수 있다.
즉, 클로저를 함수 인스턴스를 만들어내는 특수한 함수로 해석할 수 있는 것이다.
이렇게 되면 클로저를 호출하는 것이 클로저 인스턴스를 생성하는 것이 된다.

클로저를 호출하면 단순히 익명 함수가 반환되는 것이 아니라
익명 함수와 함께 거기게 연결된 닫혀진 공간이 함께 반환된다.
그렇기 때문에 새로운 인스턴스를 할당한 후 호출해서 결과를 보면 내부 변수 값이 초기화 된다.
즉, 다른 인스턴스끼리는 내부 변수에 접근할 수 없는 것이다.

결론적으로 오직 하나의 메소드를 가지고 있는 오브젝트를 사용하는 곳에 일반적으로 클로저를 사용할 수 있다.

웹 프로그래밍을 살펴보면,
많은 자바스크립트코드가 이벤트를 기반으로 짜여진다.
코드를 통해 예제를 보자.
code>
1
2
3
4
5
6
7
8
9
10
11
12
13
function makeSizer(size){
  return function( ){
    document.body.style.fontsize = size + ‘px’;
  };
}
 
var size12 = makeSizer(12);
var size14 = makeSizer(14);
var size16 = makeSizer(16);
 
document.getElementById(‘size-12’).onclick = size12;
document.getElementById(‘size-14’).onclick = size14;
document.getElementById(‘size-16’).onclick = size16;
cs

이런 식으로 활용할 수 있다.





#포스팅 내용은 황인균 님의 자바스크립트 객체지향 프로그래밍 이라는 책의 내용을 기반으로 작성되었습니다.
Chapter 6. 끝