클로저 #6
Replies: 5 comments
-
클로저의 의미 및 원리 이해클로저(Closure)는 여러 함수형 프로그래밍 언어에서 등장하는 보편적인 특성입니다. 다양한 문헌에서 제각각 클로저를 다르게 정의 또는 설명하고 있습니다. 더구나 클로저를 설명하는 문장 자체도 이해하기 어려운 단어가 등장하는 경우가 많습니다. MDN(Mozilla Developer Network)에서는 클로저에 대해 “A closure is the combination of a function and the lexical environment within which that function was declared”라고 소개합니다. “클로저는 함수와 그 함수가 선언될 당시의 lexical environment의 상호관계에 따른 현상”
지금까지 파악한 내용을 한 줄로 표현하면 “어떤 함수에서 선언한 변수를 참조하는 내부함수에서만 발생하는 현상” 이라고 할 수 있습니다. // 예제 5-1
var outer = function () {
var a = 1;
var inner = function () {
console.log(++a);
};
inner();
};
outer(); // 2
// 예제 5-2
var outer = function () {
var a = 1;
var inner = function () {
return ++a;
};
return inner();
};
var outer2 = outer();
console.log(outer2); // 2
예제 5-1, 5-2는 outer 함수의 실행 컨텍스트가 종료되기 이전에 inner 함수의 실행 컨텍스트가 종료돼 있으며, 이후 별도로 inner 함수를 호출할 수 없다는 공통점이 있습니다. // 예제 5-3
var outer = function () { // 1번째 줄
var a = 1; // 2번째 줄
var inner = function () { // 3번째 줄
return ++a; // 4번째 줄
}; // 5번째 줄
return inner; // 6번째 줄
}; // 7번째 줄
var outer2 = outer(); // 8번째 줄
console.log(outer2()); // 9번째 줄
console.log(outer2()); // 10번째 줄 예제 5-2와 다르게 inner()를 실행하는 대신, inner 함수 자체를 return 하도록 변경하였습니다.
그런데 말입니다 inner 함수의 실행 시점에는 outer 함수는 이미 실행이 종료된 상태인데 outer 함수의 LexicalEnvironment에 어떻게 접근할 수 있는 것일까요? 이는 가비지 컬렉터의 동작방식 때문입니다. 가비지 컬렉터는 어떤 값을 참조하는 변수가 하나라도 있다면 그 값은 수집 대상에 포함시키지 않습니다.
이처럼 함수의 실행 컨텍스트가 종료된 후에도 LexicalEnvironment가 가비지 컬렉터의 수집 대상에서 제외되는 경우는 5-3과 같이 지역변수를 참조하는 내부함수가 외부로 전달된 경우가 유일합니다. 이를 바탕으로 정의를 다시 고쳐보면 이렇습니다. 클로저란 어떤 함수 A에서 선언한 변수 a를 참조하는 내부함수 B를 외부로 전달할 경우 A의 실행 컨텍스트가 종료된 이후에도 변수 a가 사라지지 않는 현상을 말합니다. 보통 다른 책에서는 이렇게 클로저를 정의하곤 합니다.
클로저의 정의에 가장 근접한 3가지 정의이지만, 살짝 아쉬운 부분이 있습니다. 개념적으로 클로저는 어떤 상황에서만 발생하는 특수한 ‘현상’을 의미합니다. 함수는 이 현상이 나타나기 위한 ‘조건’에는 해당하지만, 그 현상을 구체화한 ‘대상’으로 볼 수는 없습니다. 따라서 실제적인 클로저는 ‘클로저 현상에 의해 메모리에 남겨진 변수들의 집합’을 지칭하는 것으로 이해하는 것이 좀 더 정확할 것입니다. 여기서 주의할 점은 ‘외부의 전달’이 곧 return만을 의미하는 것은 아니라는 점입니다. 두가지 예시를 살펴보겠습니다. (function () {
var a = 0;
var intervalId = null;
var inner = function () {
if (++a >= 10) {
clearInterval(intervalId);
}
console.log(a);
};
intervalId = setInterval(inner, 1000);
})();
(function () {
var count = 0;
var button = document.createElement('button');
button.innerText = 'click';
button.addEventListener('click', function () {
console.log(++count, 'times clicked');
});
document.body.appendChild(button);
})();
두 상황 모두 지역변수를 참조하는 내부함수를 외부에 전달했기 때문에 클로저입니다. |
Beta Was this translation helpful? Give feedback.
-
클로저와 메모리 관리클로저란, 내부 함수가 참조하는 변수가 사라지지 않는 현상을 말한다. 기본적으로 실행 컨텍스트가 종료됨에 따라 함수 내의 식별자 영역인 LexicalEnvironment 또한 사용하던 메모리가 반환된다. 하지만 내부함수가 참조하고 있는 변수가 존재하게 되면 해당 변수는 실행 컨텍스트가 콜스택에서 POP되면서 메모리가 반환되지 않고 그대로 유지된다. 내부함수가 존재하는 한, 참조하는 변수들은 GC되지 않기 때문에 의도하지 않은 경우 메모리 누수가 발생할 수 있다. 그러므로 필요하지 않은 경우에는 반드시 내부함수를 기본형 데이터를 할당하여 GC의 대상이 되도록 해야한다. 왜 내부함수를 기본형 데이터를 할당하는 작업이 GC의 대상이 되도록 하는걸까? 이를 위해 GC방식에 대해 알아보았다. Garbage Collection
mark-and-sweep
즉, 내부 함수가 기본형 데이터로 재할당 되면서 변수를 참조하는 로직이 GC가 된다. 이를 통해 기존에 참조되던 변수들의 예제를 살펴보면, (function() {
var a = 0;
var intervalId = null;
var inner = function() {
if(++a >= 10) {
clearInterval(intervalId);
inner = null;
}
console.log(a);
}
intervalId = setInterval(inner,1000);
})();
10회가 완료되면, clearInterval()에 의해 |
Beta Was this translation helpful? Give feedback.
-
Beta Was this translation helpful? Give feedback.
-
내부함수를 외부로 전달하는 방법에는 함수를 return하는 경우뿐 아니라 콜백으로 전달하는 경우도 포함됩니다. 두 경우에 대해 알아봅시다. 5-3-3 부분 적용 함수부분 적용 함수(partially applied function)는 여러 개의 인자를 받는 함수가 있을 때 일부의 인자를 고정한 함수를 만드는 기법이다. const plus = function(a, b, c) {
return a + b + c;
};
Function.prototype.partial = function() {
const args = [].slice.apply(arguments);
const self = this;
return function() {
return self.apply(null, args.concat([].slice.apply(arguments)));
};
}; 일부 인자만 받은 새로운 함수를 만들고, 나중에 새로운 함수에 인자를 넣어 완성합니다. const plusa = plus.partial(1);
plusa(2, 3); // 6
const plusb = plusa.partial(2);
plusb(4); // 7
const plusab = plus.partial(1, 3);
plusab(5); // 9 bind를 사용하면 더 깔끔하다. const plusa = plus.bind(null, 1);
plusa(2, 3); // 6
const plusb = plusa.bind(null, 2);
plusb(4); // 7
const plusab = plus.bind(null, 1, 3);
plusab(5); // 9 5-3-4 커링 함수커링 함수(currying function)이란 여러 개의 인자를 받는 함수를 하나의 인자만 받는 함수로 나눠서 순차적으로 호출될 수 있게 체인 형태로 구성한 것을 말합니다.
currying도 partial application처럼 인자를 미리 고정할 수 있지만 하나씩만 고정한다는 것이 특징입니다. 매 번 인자를 1개씩 고정하는 연속적인 partial application으로 볼 수도 있습니다. const curry3 = function (func) {
return function (a) {
return function (b) {
return func(a, b)
}
}
}
const getMaxWith10 = curry3(Math.max)(10);
console.log(getMaxWith10(8)); // 10
console.log(getMaxWith10(25)); // 25 각 단계에서 받은 인자들은 모두 마지막 단계에서 참조할 것이므로 GC되지 않고, 메모리에 차곡차곡 쌓였다가, 마지막 호출로 실행 컨텍스트가 종류된 후에야 비로소 한꺼번에 GC의 수거 대상이 됩니다. 지연 실행 (lazy execution)커링 함수가 함수형 프로그래밍의 지연실행에서 유용하다. 지연 실행은 당장 필요한 정보만 받아서 전달하고 또 필요한 정보가 들어오면 전달하다가 마지막 인자가 넘어갈 때까지 함수 실행을 미루는 것을 의미한다. const getInformation = baseUrl => path => id => fetch(baseUrl + path + '/' + id); 보통 RESTFUL API를 이용할 경우 baseUrl은 몇개로 고정되지만 나머지 path나 id 값은 매우 많을 수 있다. 매번 baseUrl부터 전부 기입해주기보다는 공통적인 요소는 먼저 기억시켜두고 특정한 값만으로 서버 요청을 수행하는 함수를 만들어두는 편이 개발 효율성이나 가독성 측면에서 좋습니다. const imageUrl = 'http://imageAddress.com/'
const productUrl = 'http://productAddress.com/'
const getImage = getInformation(imageUrl);
const getEmoticon = getImage('emoticon'); // http://imageAddress.com/emoticon
const getIcon = getImage('icon'); // http://imageAddress.com/icon
const emoticon1 = getEmoticon(100); // http://imageAddress.com/emoticon/100
const emoticon2 = getEmoticon(102); // http://imageAddress.com/emoticon/102 위와 같은 이유로 Flux 아키텍처의 구현체 중 하나인 Redux의 미들웨어에서도 커링은 사용됩니다. const logger = store => next => action => {
console.log('dispatching', action);
console.log('next state', store.getState());
return next(action);
}
const thunk = store => next => action => {
return typeof action === 'function'
? action(dispatch, store.getState)
: next(action);
}; 두 미들웨어는 공통적으로 store, next, action 순서로 인자를 받습니다. store와 next는 프로젝트 내에서 한 번 생성된 이후로는 바뀌지 않는 속성입니다. action의 경우는 매번 달라집니다. store와 next 값이 결정되면 커링을 사용해서 Redux 내부에 logger, thunk에 미리 넘겨서 반환된 함수를 저장시켜놓고, 이후에는 action만 받아서 처리할 수 있습니다. |
Beta Was this translation helpful? Give feedback.
-
이번주 주제는 '클로저' 입니다. 아래 주제 중 1개를 선택하여 작성해주세요.
주제 1
5-1 클로저의 의미 및 원리 이해
주제2
5-2 클로저와 메모리 관리
주제3
5-3 클로저 활용 사례
5-3-1 콜백 함수 내부에서 외부 데이터를 사용하고자 할 때
5-3-2 접근 권한 제어(정보 은닉)
주제4
5-3 클로저 활용 사례
5-3-3 부분 적용 함수
5-3-4 커링 함수
주제5
5-4 정리 + 자율주제
Beta Was this translation helpful? Give feedback.
All reactions