-
Notifications
You must be signed in to change notification settings - Fork 13
javascript closures by example
클로저를 사용하는 법을 알면 매우 유용하다. 문제는 많은 자바스크립트 개발자가 클로저를 사용하는 법을 모른다는 것이다. 이 가이드를 읽은 후 클로저가 무엇이고 어떻게 작동하는지, 언제 사용하는 지 잘 이해하길 바란다.
MDN(Mozilla Developer Network)에 나와 있는 클로저의 정의를 인용하자면, "클로저는 독립적인 변수를 참조하는 함수이다. 다시 말해 클로저에 정의 된 함수는 생성된 환경을 '기억'한다." 아래의 예를 보자.
var outerFunc = function() {
var message = "Hello, World!";
var innerFunc = function() {
return message;
}
return innerFunc;
}
자바스크립트는 다른 언어처럼 블록 레벨 범위 지정(scoping)이 아닌, 함수 레벨 범위 지정을 구현한다. 따라서 위의 예에서 outerFunc내에 정의 된 변수 는 해당 함수의 스코프가 로컬이므로 함수 외부에서는 액세스 할 수 없다. 그리고 일반적으로 함수 실행이 끝나면 로컬 변수가 더 이상 존재하지 않게 된다. 그러나 outerFunc 함수가 로컬 변수를 참조하는 innerFunc 를 반환하기 때문에 위 코드는 흥미롭다. outerFunc 가 실행을 마친 후에도 message 변수를 사용할 수 있을까?
> var myFunc = outerFunc();
> myFunc();
'Hello, World!'
message 변수를 사용할 수 있다. 보다시피, outerFunc는 innerFunc를 반환하고 myFunc에 저장한다. myFunc 는 클로저가 되어 생성 된 환경을 기억하기 때문에, outerFunc 가 실행될 때 message 변수 의 값을 기억한다 . 이런 관계를 모르는 상태에서 클로저를 사용했을 가능성이 있으니 알아두자!
for 루프 내에서 함수를 실행할 때 문제가 발생한 경험이 있을 것이다. 이 문제가 나에게 처음 일어 났을 때 나는 때려치우고 싶었다. 아래 예제를 살펴보자.
var names = ['Locke', 'Franklin', 'Smith', 'Mises'];
var logName = function(name) {
console.log(name);
};
var name;
for (var i=0; i < names.length; i++) {
name = names[i];
setTimeout(function(){
logName(name);
}, 1000);
}
위의 코드에서는 names 이라는 배열을 만들어 네 개의 이름 목록을 저장한다. name을 매개 변수로 받고 기록하는 logName 함수를 만들었다. 그런 다음 names을 반복하며 logName에 name을 전달하고, 해당 함수를 1초 후에 실행하는 for loop를 만들었다 . 이 예제가 정상적으로 동작한다고 생각할 수도 있지만 실제로 "Mises"를 네 번 기록한다. 그 이유는 매우 간단하다. logName은 작성된 환경을 기억하는 클로저이다. for 루프에서 logName 바로 호출 한 경우엔 모든 name을 기록한다. 그러나 우리는 대신 타임 아웃을 설정하고 클로저(setTimeout에 대한 콜백)를 만들고 있으며, 이 클로저는 환경을 기억하고 있다. 1초가 지난 뒤, name 변수가 "Mises"로 설정되었다. 따라서 단순히 "Mises"를 네 번 기록하는 것이다. 이 문제를 어떻게 해결할 수 있을까? 이 문제를 해결하는 한 가지 방법은 다른 클로저를 구현하는 것이다.
var names = ['Locke', 'Franklin', 'Smith', 'Mises'];
var logName = function(name) {
console.log(name);
};
var makeClosure = function(name) {
return function() {
logName(name);
}
};
for (var i=0; i < names.length; i++) {
var name = names[i];
setTimeout(makeClosure(name), 1000);
}
그러나 불필요한 클로저를 만드는 것은 좋지 않다. 사실, 원하는 결과를 얻을 훨씬 더 좋은 방법이 있지만 for loop의 클로저 문제와 클로저로 어떻게 해결할 수 있는지 설명하고 싶었다. 위의 코드를 실행하면 1 초 지연 후 4 개의 name이 각각이 기록되어 예상대로 작동하는 것을 볼 수 있다. 우리가 해야 할 일은 name 변수가 생성 될 때부터 '기억하는' 클로저를 반환 하는 makeClosure 함수를 만드는 것이 었다. 클로저는 즉시 만들어 지므로 각 name 의 가치를 기억한다.
많은 개발자들이 자바스크립트 클로저에 기반한 모듈 패턴을 사용한다. 이 링크에선 모듈 패턴에 대해 자세히 살펴볼 수 있지만, 여기선 기본과 클로저를 어떻게 구현하는지에 대해 설명하고자 한다. 모듈 패턴을 사용하는 목적은 코드를 정리하고, 글로벌 스코프를 깨끗하게 유지하며, 모듈 외부에서 접근해서는 안 되는 private 코드를 유지하는 것이다. 모듈 패턴은 다음과 같다.
var module = (function(){
var localVar = 1913;
var localFunc = function() {
return localVar;
}
var otherLocalFunc = function(num) {
localVar = num;
}
return {
getVar: localFunc,
setVar: otherLocalFunc
}
})();
위 예제에서 익명 함수 안에 단일 환경을 만들었다. 이 환경에는 로컬 변수 localVar 및 로컬 함수 표현식 localFunc가 포함 된다. 이 변수 중 어느 것도 익명 함수 외부에서 접근 할 수 없으므로 전역 네임 스페이스를 깨끗하게 유지할 수 있다. 익명 함수 외부에서 접근할 수 있는 방법은 익명 함수에 의해 반환되어야한다. 위 예에서 볼 수 있듯이, localFunc을 값으로 가지는getVar라는 key와, otherLocalFunc를 값으로 가지는 setVar 라는 key 가진 객체를 반환한다. 이 패턴에 대한 좋은 점은 모듈의 state가 애플리케이션 수명 동안 지속된다. 이것은 localVar를 외부에서 직접 액세스 할 수 없지만, 반환된 함수는 여전히이 변수에 접근 할 수 있음을 의미한다. 위의 코드를 사용하여 아래 예제를 살펴보라.
console.log(module.getVar());
> 1913
module.setVar(1776);
console.log(module.getVar());
> 1776
자바스크립트에서 private 변수 localVar를 성공적으로 에뮬레이션하고, 전역 네임 스페이스를 깨끗하게 유지했다 . 여기서 유일한 전역 변수는 module 이다. 이 모든 것을 클로저를 사용하여 구현했다. 이 글의 범위에 포함되지는 않지만 이 패턴은 IIFE (즉시 호출 함수 표현식)를 사용했다. 익명 함수가 (function () {...}) () 과 같이 괄호로 묶여 있음을 볼 수있다. 괄호가 없으면 이것은 단순히 함수 정의 일 것입니다. 그러나 괄호를 추가하면 (뒤에 나오는 괄호에도 유의) 즉시 호출 함수 표현식이 된다. 이에 대한 자세한 내용은 여기를 참조 하라.
좀 더 실용적인 예가 있다. 즉석에서 함수를 만들고 싶다고 가정해보자. 함수 팩토리(함수를 만드는 함수)를 만들 수 있다. 함수 팩토리의 결과 함수는 작성된 환경을 기억하는 클로저 이다.
var functionFactory = function(num1) {
return function(num2) {
return num1 * num2;
}
}
위의 functionFactory 함수에 하나의 숫자를 넘길 수 있다. 그런 다음 functionFactory는 원래 전달 된 num1 값을 기억하는 클로저를 리턴한다 . 결과 함수는 기존 num1에 클로저 호출 시 전달 된 num2 값을 곱한다 .
var mult5 = functionFactory(5);
var mult10 = functionFactory(10);
위의 간단한 함수는 새로운 함수 mult5 및 mult10을 만든다 . 이제 5 또는 10을 곱하려는 숫자를 전달하여 이러한 함수 중 하나를 호출 할 수 있다. 이제 결과를 예상 할 수 있다.
> mult5(3)
15
> mult5(5)
25
> mult10(3)
30
> mult10(5)
50
이 가이드가 유용하다고 생각되면 아래에 알려주기 바란다. 또한 오류를 발견 하거나 더 독특한 예제를 제공 할 수 있는 경우 의견을 보내주기 바란다.
일부 의역이 들어간 경우도 있으므로 해당 원문의 내용과 조금 다를 수 있습니다.
문제가 될 소지가 있다거나 혹은 수정이 필요한 사항이 있다면 있다면 issues 보내주세요.
기술문서
- 호출스택
- 원시자료형
- 값타입과 참조타입
- 명시적 변환, 암시적 변환, Nominal, 구조화, 덕 타이핑
- == vs === vs typeof
- 함수 범위, 블록 범위, 렉시컬(lexical) 범위
- 식(expression) vs 문(statement)
- IIFF, Modules, Namespaces
- 메세지큐와 이벤트루프
- setTimeout, setInterval, requestAnimationFrame
- 자바스크립트 엔진
- 비트 연산자, 형식화 배열, 버퍼(배열)
- DOM과 Layout Trees
- 팩토리와 클래스
- this, call, apply, bind
- new, 생성자, instanceof, 인스턴스
- 프로토타입의 상속과 체인
- Object.create와 Object.assign
- map, reduce, filter
- 순수함수, 부수효과, 상태변이
- Closure
- 고차함수
- 재귀
- 컬렉션과 생성기
- Promise
- async, await
- 자료구조
- 함수 성능과 빅 오 표기법
- 알고리즘
- 상속, 다형성, 코드의 재사용성
- 설계패턴
- 부분 어플리케이션, 커링, Compose, Pipe
- 클린코드