closure란?
12 Mar 2019해당 포스트는 Master the JavaScript Interview: What is a Closure?를 번역한 컨텐츠입니다. 잘못된 부분이 있다면 댓글 부탁드립니다.
“마스터 자바스크립트 인터뷰”는 중급이나 고급레벨의 자바스크립트 포지션 면접질문을 위해 만들어진 포스팅입니다. 그리고 필자가 실제 인터뷰에서 자주 사용합니다.
- 필자는 자바스크립트 인터뷰 질문 시리즈를 런칭합니다. 솔직히, 클로저를 모르면 자바스크립트를 정확히 알고 있다고 할 수 없습니다. 그렇게 생각 안할 수도 있지만, 복잡한 자바스크립트 어플리케이션이 어떻게 빌드되는지 정확하게 알고 있습니까? 당신이 정말 그것을 알고 있거나 어떻게 어플리케이션 동작하는지 알고 있습니까? 필자는 항상 의구심을 가지고 있습니다. 이 질문에 대답을 모른다면 심각한 상태입니다. 클로저의 메커니즘 뿐만아니라, 왜 중요하고 클로저 사용 사례에 대해 쉽게 대답을 할 수 있어야 합니다. 클로저는 객체 데이터 은닉을 위해 빈번하게 사용됩니다. 이벤트 핸들러, 콜백함수, 부분 응용 어플리케이션 ***, 커링 ***, 함수형 프로그래밍 패턴에서 사용합니다. 지원자가 클로저에대한 개념이나 기술적인 정의에 대해서 아는 것은 중요하지 않습니다. 필자는 그들이 기본적인 메커니즘을 아는지 확인하고 싶어 합니다. 만약 그렇지 않다면 그것은 개발자가 실제 자바스크립트 어플리케이션을 빌드해본 경험없이 없다고 간주합니다.
만약 이 질문에 대답을 못한다면, 경력이 얼마든지 당신을 주니어 개발자입니다!
- 심하다고 생각할 수 있지만 그렇지 않습니다. 필자의 의도는 유능한 면접관은 클로저가 무엇인가 질문할 것이고 잘못된 대답을 한다면 떨어질 것입니다. 혹 운좋게 면접을 통과해도 경력과 관계없이 주니어 개발자로 고용되기때문에 연봉이 낮을 것입니다.
- 클로저에 대한 일반적인 사용법을 준비해야 합니다.
클로저란 무엇인가?
- 클로저는 렉시컬 환경을 참조하고 있는 함수들이 합쳐진 집합입니다. 즉, 클로저는 내부함수에서 외부함수의 스코프에 접근할 수 있는 것입니다. 자바스크립에서 클로저는 함수가 생성될 때마다 생성됩니다. 클로저를 사용하기 위해서 다른 함수안에 함수를 정의하고 노출시켜야 합니다. 노출하기위해서 그것을 리턴하거나 다른 함수에 전달하면 됩니다. 내부함수는 외부함수가 반환되었더라도 외부함수 스코프 변수에 접근할 수 있습니다.
클로저 사용 예시
- 클로저는 객체 데이터 은닉시킬떼 주로 사용됩니다. 데이터 은닉은 프로그램 구현이 아니라 인터페이스를 구성하는데 필수적인 요소입니다. 이것은 견고한 소프트웨어를 빌드하는데 중요한 개념입니다. 왜냐하면 구현 디테일은 인터페이스 컨텍트보다 파괴적인 방식으로 변경되기가 쉽습니다.***
“프로그램 인터페이스는 구현이 아니다.” 디자인패턴: 재사용 가능한 객체 지향형 소프트웨어의 요소
- 자바스크립트에서 클로저는 데이터 은닉을 할 수 있는 중요한 한 메커니즘입니다. 정보은닉을 위해 클로저를 사용할 떼 감싸여진 변수는 포함된 함수(외부함수) 스코프 내에 있습니다. 그 밖에 스코프에서 특수한 방법을 제외하고 데이터를 참조할 수 없습니다. 자바스크립트에서 클로저 스코프 외부로 노출시키는 방법이 정의되어 있고 예를 들면 다음과 같습니다.
const getSecret = secret => {
return {
get: () => secret
};
};
test("객체 은닉을 위한 클로저", assert => {
const msg = ".get()은 클로저에 접근할 수 있습니다.";
const expected = 1;
const obj = getSecret(1);
const actual = obj.get();
try {
assert.ok(secret, "에러");
} catch (e) {
assert.ok(true, "secret var는 예외적인 방법으로 유효합니다.");
}
assert.equal(actual, expected, msg);
assert.end();
});
- 위에 예시를 보면 ‘.get()’메소드는 ‘getSecret()’내부에 선언되었기 때문에 ‘getSecret()’ 변수에 접근할 수 있고, 이를 데이터 은닉 방법으로 사용합니다. 이 경우 파라미터는 ‘secret’입니다. 객체는 데이터 은닉에 사용하는 유일한 방법이 아닙니다. 클로저 또한 내부 상태에서 변환값에 영향을 주는 stateful 함수를 생성할 때 사용할 수 있습니다.
const secret = msg => () => msg;
// Secret - Secret 메세지로 클로저를 생성합니다.
// https://gist.github.com/ericelliott/f6a87bc41de31562d0f9
// https://jsbin.com/hitusu/edit?html,js,output
// secret(msg: String) => getSecret() => msg: String
const secret = msg => () => msg;
test("secret", assert => {
const msg = "secret()은 secret을 통과한 함수를 리턴합니다.";
const theSecret = "클로저는 쉽습니다.";
const mySecret = secret(theSecret);
const actual = mySecret();
const expected = theSecret;
assert.equal(actual, expected, msg);
assert.end();
});
-
함수형 프로그램에서 클로저는 Partial Application과 커링을 위한 빈번하게 사용됩니다. 이런 요구사항은 약간의 정의가 필요합니다.
-
Application: arguments를 값을 리턴하기 위해 함수를 적용하는 프로세스입니다.
-
Partial Application: arguments로 함수를 적용하는 프로세스입니다. 부분적으로 적용된 함수는 나중에 반환되기위해 사용됩니다. 즉, 여러개의 parameters를 가지고 있고 더 적은 parameters를 반환하는 함수입니다. partial application은 반환된 함수내에서 하나 혹은 그 이상의 arguments를 고정합니다. 그리고 반환된 함수는 함수 어플리케이션을 완성하기 위해 나머지 parameters를 arguments로 사용합니다.
-
partial application은 parameters를 고정하기위해 클로저 스코프 이접을 취하고 있습니다. target function에 부분적으로 arguments를 적용하는 일반함수를 사용할 수 있습니다. 다음과 같은 형태를 가집니다.
partialApply(targetFunction: Function, ...fixedArgs: Any[])=> functionWithFewerParams(...remainingArgs: Any[])
-
만약 위에 함수를 읽는데 도움이 필요하다면 Rtype: Reading Function Signatures를 살펴보세요.
-
그것은 여러개의 arguments를 취할 수 있는 함수이고, 뒤에 있는 arguments는 우리가 부분적으로 적용하기를 원하는 함수입니다. 그리고 나머지 arguments를 가지고 있는 함수를 반환합니다.
아래 예제를 보면 도움이 될 겁니다. 2개의 수를 더하는 함수입니다.
const add = (a, b) => a + b;
- 당신은 10과 다른 수를 더하는 함수를 원한다고 가정해 봅시다. 우리는 그것을 ‘add10()’이라고 정합니다. ‘add10(5)’의 결과는 15가 되어야 합니다. 우리의’partialApply()’함수는 아래와 같이 생성합니다.
const add10 = partialApply(add, 10);
add10(5);
-
예를 들어, argument 10는 고정된 parameter가 되고 ‘add10()’ 클로져 스코드 안에 기억될 겁니다.
-
‘partialApply()’ 구현을 살펴봅시다.
// 일반적인 Partial Application 함수
// https://jsbin.com/biyupu/edit?html,js,output
// https://gist.github.com/ericelliott/f0a8fd662111ea2f569e
// partialApply(targetFunction: Function, ...fixedArgs: Any[]) =>
// functionWithFewerParams(...remainingArgs: Any[])
const partialApply = (fn, ...fixedArgs) => {
return function(...remainingArgs) {
return fn.apply(this, fixedArgs.concat(remainingArgs));
};
};
test("add10", assert => {
const msg = "partialApply()는 함수에서 부분적용되어야 합니다.";
const add = (a, b) => a + b;
const add10 = partialApply(add, 10);
const actual = add10(5);
const expected = 15;
assert.equal(actual, expected, msg);
});
- 보시다시피,’partialApply()’를 통과한 ‘fixedArgs’ 인수에 접근을 유지하면서 함수를 간단하게 리턴합니다.