[FN-Interview] 클로저

GDana
gdana
Published in
5 min readDec 8, 2019

이 시리즈에서는 front-end-interview-handbook의 질문들에 답하는 형식으로 채워나갈 예정입니다. 그리고 정확하지 않은 정보나 이건..!;…!!!!! 싶은 부분이 있다면 언제든 댓글 혹은 연락주세요 🙏

완료된 인터뷰

📍 클로저는 무엇이며, 어떻게/왜 사용하나요?

코드를 작성하면 자바스크립트 엔진은 각각의 변수와 함수를 접근하고 관리할 수 있는 렉시컬 환경(스코프)를 구성한다. 이 때 함수 안에 함수가 있는 외부함수와 중첩함수가 선언되어 있다면 엔진은 외부함수의 지역변수들에 대해서 식별자 결정을 한다. 식별자 결정을 통해서 중첩함수가 참조하는 변수가 있다면 해당 변수는 중첩함수의 자유변수가 되고 중첩함수는 클로저가 된다.

이렇게 외부에서 직접 접근하지 못하는 특징덕분에 객체 지향 프로그래밍의 정보 은닉 방법인 캡슐화가 가능해진다.

클로저란

클로저는 함수가 어디에 선언되어 있느냐에 따라 스코프가 결정되는데, 스코프 밖에서 호출해도 자신의 스코프를 기억하는 함수가 클로저이다.

var a = ‘A’function outer(){
var b = ‘B’
function inner(){
var c = ‘C’
console.log(a+b+c)
}
return inner
}
var counter = outer()
console.log(counter()) //ABC

렉시컬 환경이란 클로저가 선언되었을 때의 스코프를 뜻하는데 inner()의 경우, 함수 안쪽에서 바깥쪽으로는 접근할 수는 있어도 함수가 다른 함수 안쪽을 접근할 수 없는 규칙 때문에 inner()의 렉시컬 환경은 var a = ‘A'가 있는 전역까지 넓혀질 수 있다.

이때 inner()가 outer()의 스코프에서 참조하고 있는 변수 var b = ‘B'자유변수라 하고, 자유변수에 엮여있는 함수인 inner()클로저라고 한다.

클로저의 사용

겹치지 않는 변수명

아래 예제를 보면 전역변수에, outer()의 지역변수에 a가 있다. counter()를 실행하면 과연 어떻게 될까?

var a = 'A'function outer(){
var a = 'Aa'
var b = 'B'
return function(){
var c = 'C'
console.log(a+b+c)
}
}
var counter = outer()

위에서부터 읽어내려왔다면 클로저는 전역에까지 스코프 체인이 된다고 생각할지도 모르겠다. 하지만 위의 예제와 같이 동일한 이름의 변수를 사용할 경우 충돌이 일어나기 때문에 식별자 검사를 통해 클로저의 참조가 될 변수를 결정짓게 된다.

검사는 엔진이 진행하며 식별자 결정 규칙에 의해 안쪽 코드에 선언된 변수를 사용하게 된다.

console.log(counter()) //AaBC

외부로 부터 정보 은닉은 캡슐화

아래 예제는 counter()를 실행할 때마다 count의 값이 증가되는 함수로, 클로저가 왜 외부에서 접근할 수 없는 캡슐화된 객체인지 예제로 살펴볼 수 있다.

function makeCounter(){
var count = 0;
return {
button : function(){
return count++
}
}
}
var counter = makeCounter()

예제에서 클로저가 아닌 직접 접근하면 아래 코드와 같은 에러가 발생한다. 이유를 알아보자.

console.log(makeCounter.count) //undefinedconsole.log(makeCounter.button()) 
//Uncaught TypeError: makeCounter.button is not a function

1. 함수는 서로의 스코프에 접근할 수 없다.

function makeCounter(){
var count = 0;
return function(){
return count++
}
}
function resultCounter(){
var str = ‘점 입니다’;
console.log(makeCount() + str)
}

함수는 스코프 규칙에 의해서 각각 선언된 서로의 함수에 접근할 수 없다. 혹여나 다른 함수에서 참조 되었다 하더라도 불가능하다.

resultCounter() 
//Uncaught ReferenceError: makeCount is not defined
var counter = resultCounter()
//Uncaught ReferenceError: makeCount is not defined

2. 하지만 함수 내부에 선언된 함수는 바깥 함수에 접근이 가능 하다.

console.log(counter.button()) //0
console.log(counter.button()) //1
console.log(counter.button()) //2

메모리 정리의 대상이 되지 않는다

더이상 참조하는 값이 없는 사용 완료된 메모리를 자바스크립트 엔진이 자동으로 삭제해주는 것을 가비지 컬렉션이라 한다. 하지만 클로저의 경우 함수 객체가 지워지지 않는 이상 가비지 컬렉션의 대상이 되지 않는다. 때문에 클로저를 사용할 땐 가비지 컬렉션에 대한 염두를 두고 사용해야 한다.

글을 마치며 클로저의 특징을 정리하면 아래와 같다.

  1. 클로저는 외부에서 접근이 불가하다 이 말은 클로저를 담은 외부 함수도 마찬가지이다.
  2. 그렇다면 private한 정보 또한 담을 수 있는 것이 클로저
  3. makeCounter() 코드를 빌려, 클로저 button()은 객체의 메소드와 같은 역할, button()에 의해 상태가 바뀐 count는 객체의 프로퍼티와 같은 역할을 하는 것으로 매칭해보면, 객체 지향 프로그래밍에서 객체의 프로퍼티를 외부로부터 은폐하는 ‘캡슐화’가 왜 클로저를 통해 이뤄질 수 있는지 이해할 수 있다.
  4. 클로저도 결국 ‘참조’로 이루어져 있기 때문에 더이상 참조하는 값이 없다면 가비지 컬렉션의 대상이 될 수 있다. 반대로 참조하는 값을 직접 지우는 것이 아니라면 클로저를 사용할 땐 항상 참조가 일어나기 때문에 가비지 컬렉션 대상에서 제외됨을 인지하고 사용해야 한다.

--

--