클로저
클로저(Closure) 는 여러 함수형 프로그래밍 언어에서 등장하는 보편적인 특성이다.
클로저란, 어떤 함수에서 선언한 변수를 참조하는 내부함수에서만 발생하는 현상이다.
어려운 내용인 듯하니, 예제를 통해 살펴보자.
var outer = function() {
var a = 1;
var inner = function() {
return ++a;
};
return inner();
};
var outer2 = outer();
console.log(outer2); // 2
outer 라는 함수에 지역변수 a가 선언되어있는데, outer 함수의 실행 컨텍스트가 종료된 시점에도 a 변수는 사라지지 않고 1이 증가된 모습을 확인할 수 있다.
inner 함수 실행시점에 outer 함수는 실행이 종료된 상태임에도 불구하고 LexicalEnvironment 에 접근이 가능한 이유는 가비지 컬렉터(Garbage Collector) 의 동작 방식 때문이다. 가비지 컬렉터는 어떤 값을 참조하는 변수가 1개라도 존재한다면 그 값은 수집 대상에 포함시키지 않는 것이다.
클로저라는 용어를 다시 한번 쉽게 설명하면, 어떤 함수 A에서 선언한 변수 a 를 참조하는 내부함수 B를 외부로 전달할 경우 A의 실행 컨텍스트가 종료된 이후에도 변수 a가 사라지지 않는 현상 이다.
클로저와 메모리 관리
클로저는 객체지향과 함수형을 모두 아우르는 중요한 개념이지만, '메모리 누수' 특성을 정확히 이해하고 활용해야 한다.
여기서 메모리 누수란, 개발자의 의도와 다르게 어떤 값의 참조 카운트가 0이 되지 않아 GC (Garbage Collector) 의 수거 대상이 되지 않는 경우를 말한다.
클로저의 메모리 관리 방법에 대해 알아보자. 클로저는 어떤 필요에 의해 의도적으로 함수의 지역변수 메모리를 소모함으로써 발생한다.
그렇다면 그 필요성이 사라진 시점에는 더 이상 메모리를 소모하지 않도록 해주면 된다. 위에서 설명한 바와 같이 참조 카운트를 0으로 만들면 된다.
참조 카운트를 0으로 만드는 방법은 식별자에 참조형이 아닌 기본형 데이터 (null 이나 undefined) 를 할당하면 되는 것이다.
예시로 return 에 의한 클로저의 메모리 해제를 살펴보자.
var outer = function() {
var a = 1;
var inner = function() {
return ++a;
};
return inner();
};
console.log(outer()); // 2
console.log(outer()); // 2
outer = null; // outer 식별자의 inner 함수 참조를 끊음
클로저의 활용
그렇다면, 클로저를 어떻게 활용할 수 있을까?
콜백함수에 추가적인 값들을 넘겨주거나 처음에 초기화한 값을 계속 유지하고 싶을 경우이다. 예시로 코드를 확인해보자.
var fruits = ['apple', 'banana', 'orange'];
var $ul = document.createElement('ul'); //공통코드
var alertFruit = function(fruit) {
alert('your choice is ' + fruit);
};
fruits.forEach(function (fruit) {
var $li = document.createElement('li');
$li.innerText = fruit;
$li.addEventListener('click', alertFruit.bind(null, fruit));
$ul.appendChild($li);
});
document.body.appendChild($ul);
alertFruit(fruits[1]);
반복적으로 동일한 작업을 할 때 또는 초기화 작업이 지속적으로 필요할 때, 콜백함수에 동적인 데이터를 넘겨주어야 할 때 클로저를 사용하면 된다.
접근 권한 제어 (정보 은닉)
접근 제한 권한에는 public, private, protected 의 3가지 종류가 있다.
public 은 외부에서 접근가능한 것, private 는 내부에서만 사용하고 외부에서는 노출되지 않는 것이다. protected 는 상속받은 클래스 또는 같은 패키지에서만 접근이 가능한 것이다.
JavaScript 는 기본적으로 변수 자체에 위와 같은 접근권한을 직접 부여하도록 설계되어있지는 않지만, 클로저를 활용한다면 함수차원에서 public한 값과 private한 값을 구분하는 것이 가능하다. return 이 값을 외부에 정보를 제공하는 유일한 수단이 되는 것이다.
return 한 변수들은 공개멤버(public member)가 되고, 그렇지 않은 변수들은 비공개 변수(private member)가 되는 것이다.
아래는 그 예시이다.
var outer = function () {
var a = 1
var inner = function () {
return ++a
};
return inner;
};
var outerFunc = outer();
console.log(outerFunc());
부분 적용 함수 (partially applied function)
n개의 인자를 받는 함수에 미리 m개의 인자만 넘겨 기억시켰다가 나중 (n-m)개의 인자를 넘기면 비로소 원래 함수의 실행 결과를 얻을 수 있게끔 하는 함수. 일단 개념만 알고 넘어가자... 총총
커링함수 (currying function)
커링함수란, 여러 개의 인자를 받는 함수를 하나의 인자만 받는 함수로 나눠 순차적으로 호출할 수 있게 체인 형태로 구성한 것이다.
위에 설명한 부분 적용 함수와 기본적인 맥락은 일치하나, 몇 가지 다른 점이 있다. 커링은 한 번에 하나의 인자만 전달하는 것을 원칙으로 한다.
var curry = function(func) {
return function(a) {
return function(b) {
return func(a, b);
};
};
};
var getMaxWithNum = curry(Math.max)(10);
console.log(getMaxWithNum(9)); // 10
console.log(getMaxWithNum(15)); // 15
console.log(getMaxWithNum(2)); // 10
부분 적용 함수와는 다르게 커링 함수는 필요한 상황에 직접 만들어서 쓰기에 효과적이다. 필요한 인자 개수만큼 함수를 만들어서 계속 리턴하다가 마지막에 조합하여 리턴을 해주면 되기 때문이다. 인자가 많아지면 많아질수록 가독성이 떨어진다는 점만 기억하자.
그렇지만 ES6 의 화살표 함수를 적극활용한다면 코드수를 줄일 수 있다.
var curry = func => a => b => c => d => e => func(a, b, c, d, e);
화살표 함수를 따라 값을 차례로 넘거주면 마지막에 func 가 호출되는 것을 한눈에 확인할 수 있다.
각 단계에서 넘겨받은 인자들이 마지막 단계에서 참조할 것이니 GC 되지 않고 쌓이다가 마지막 func 에서의 호출로실행 컨텍스트라 종료된 후에 한꺼번에 GC 수거대상이 되는 것이다.
커링함수는 당장 필요한 정보만 받아서 전달하고 또 필요한 정보가 들어오면 전달하는 방식으로 하면 마지막 인자가 넘어갈 때까지 함수 실행을 미루는 것이다. 이것이 바로 지연실행(lazy execution)이라고 한다. 원하는 시점까지 지연시켰다가 실행하는 것이 요긴한 상황이라면 커링을 사용하기에 적합한 것이다.
var getUrl = function(baseUrl) { // 서버에 요청할 주소의 기본 URL
return function(path) { // path 값
return function(id) { // id 값
return fetch(baseUrl + path + id); // 실제 서버에 정보를 요청
}
}
}
var getInformation = baseUrl => path => id => fetch(baseUrl + path + '/' + id);
정리
클로저는 function 안에 또 하나의 function 이 있다면 기본적으로 생성된다. 또한 scope chain 이 생성되었을 때 변수 값들을 보존하고 기억하며 함수가 메모리에서 사라질 때까지 쫓아다니게 되는 것이다. 같은 모양의 함수이더라도 다른 클로저를 가질 수 있다.
두고두고 보며 어떻게 활용할 수 있을지 계속 연구해보자.
* 출처 : '코어 자바스크립트 - 핵심 개념과 동작 원리로 이해하는 자바스크립트 프로그래밍', 정재남 지음
'Front-End > JavaScript' 카테고리의 다른 글
[JavaScript] 프로토타입(prototype)_2편 (0) | 2020.05.24 |
---|---|
[JavaScript] 프로토타입(prototype)_1편 (0) | 2020.05.06 |
[JavaScript] 콜백 함수 (Callback Function) (1) | 2020.04.04 |
[JavaScript] this 파헤치기 (0) | 2020.03.28 |
[JavaScript] 실행 컨텍스트(Execution Context) (0) | 2020.03.21 |
최근댓글