코드/JavaScript

클로저

juundev 2024. 5. 22. 15:54

간혹 코딩을 하다 클로저(Closure)라는 단어를 보긴 했다만 직접적으로 이게 무엇을 위해서 어떤 원리로 작동하는지 공부해 본 건 처음이었습니다... 

한 번 정독해서는 음,,, 이게 클로저구나,, 정도였고, 두 번째 정독할 때에는 아,, 이럴 때 이렇게 사용하면 되겠구나,, 라는 정도로 이해했습니다.

완벽한 이해를 하고싶지만 뭐든 내가 직접 문제를 해결하고자 사용한 코드가 아니라면 개념 잡기가 힘들기 때문에 최소한의 이해를 했습니다.

 

클로저의 의미 및 원리 이해

클로저(Closure)는 여러 함수형 프로그래밍 언어에서 등장하는 보편적인 특성이라고 합니다. 자바스크립트가 가지는 고유한 개념이 아니라서, 다양한 문헌에서 클로저를 다르게 정의하고 설명합니다. 

 

다양한 문헌에서 클로저를 한 문장으로 요약해서 설명하는 부분들은 다음과 같습니다.

다양한 문헌에서의 클로저 정의

 

MDN(Mozilla Developer Network)에서는 클로저에 대해서 "클로저는 함수와 그 함수가 선언될 당시의 lexical environment의 상호관계에 따른 현상"이라고 정의합니다.

조금 쉽게 말하자면, "어떤 함수에서 선언한 변수를 참조하는 내부함수에서만 발생하는 현상"이라고 합니다.

 

내가 알고있던 개념이 아닌 단어의 정의를 보면, 확 와닿지 않는 것 같습니다.

 

다음은 외부 함수의 변수를 참조하는 내부 함수의 코드입니다.

// ex1
var outer = function() {
    var a = 1;
    var inner = function() {
    	console.log(++a); // console.log: 2
    };
    inner();
};
outer();

ex1에서는 outer 함수에서 변수 a를 선언했고, outer의 내부함수인 inner 함수에서 a의 값을 1만큼 증가시킨 다음 출력했습니다.

inner 함수 내부에서는 a를 선언하지 않았기 때문에 부모 함수에 접근하여 a를 찾습니다.

 

// ex2
var outer = function() {
    var a = 1;
    var inner = function() {
    	return ++a;
    };
    return inner;
};

var outer2 = outer();
console.log(outer2()); // console.log: 2
console.log(outer2()); // console.log: 3

ex2에서는 inner 함수의 실행 결과가 아닌 inner 함수 자체를 반한 했습니다. 

그러면 outer 함수가 종료될 때 outer2 변수는 outer의 실행 결과인 inner 함수를 참조하게 됩니다. 이후 outer2를 호출하면 반환된 함수인 inner가 실행되는 것입니다.

 

예시를 바탕으로 정의를 다시 해보면 다음과 같습니다.

 

클로저란 어떤 함수 A에서 선언한 변수 a를 참조하는 내부함수 B를 외부로 전달할 경우,

A가 종료된 이후에도 변수 a가 사라지지 않는 현상

 

클로저와 메모리 관리

클로저는 객체지향과 함수형에서 매우 중요한 개념입니다. 메모리 누수의 위험을 이유로 클로저 사용을 조심해야 한다거나 지양해야 한다는 주장도 있지만, 메모리 소모는 클로저의 본질적인 특성이라고 합니다.

'메모리 누수'라는 표현읜 개발자의 의도와 달리 어떤 값의 참조 카운트가 0이 되지 않아 가비지 컬렉터의 수거 대상이 되지 않는 경우에는 맞는 표현이겠지만, 개발자가 의도적으로 참조 카운트를 0이 되지 않게 설계한 경우는 '누수'라는 표현이 본질과 맞지 않습니다.

 

클로저는 어떤 필요에 의해서 의도적으로 함수의 지역변수의 메모리를 소모하도록 함으로써 발생합니다. 그렇다면 필요성이 사라진 시점에는 더 이상 메모리를 소모하지 않게 해 주면 됩니다. 참조 카운트를 0으로 만들면 가비지 컬렉터가 수거할 것이고, 이때 소모됐던 메모리가 회수되어 회복하게 되는 원리라고 이해하면 되겠습니다.

 

다음은 클로저를 사용하면서 메모리를 관리하는 방법입니다.

// return에 의한 클로저의 메모리 해제
var outer = (function() {
	var a = 1;
    var inner = function() {
    	return ++a;
    };
    return inner;
})();

console.log(outer());
console.log(outer());
outer = null // outer 식별자의 inner 함수 참조를 끊음

 

// setInterval에 의한 클로저의 메모리 해제
(function() {
	var a = 0;
    var intervalId = null;
    var inner = function() {
    	if (++a >= 10) {
        	clearInterval(intervalId);
            inner = null; // inner 식별자의 함수 참조를 끊음
        }
        console.log(a);
    };
    intervalId = setInterval(inner, 1000);
})();

 

클로저 활용 사례

클로저의 의미와 작동 원리를 어느 정도(?) 이해했으니 제일 궁금한 어떤 상황에서 클로저가 등장하는지 코드로 확인해보겠습니다.

 

콜백 함수 내부에서 외부 데이터를 사용하고자 할 때

var fruits = ['apple', 'banana', 'peach'];
var $ul = document.createElement('ul');

fruits.forEach(function(fruit) { // A
	var $li = document.createElement('li');
    $li.innerText = fruit;
    $li.addEventListener('click', function() { // B
    	alert('선택한 과일은 ' + fruit + '입니다.');
    });
    $ul.appendChild($li);
});
document.body.appendChild($ul);

위 코드에서는 fruits 배열을 순회하며 리스트 아이템을 생성하고, 각 리스트 아이템을 클릭하면 해당 리스너에 기억된 콜백 함수를 실행하게 했습니다.

forEach 메서드에 넘겨준 익명의 콜백 함수 (A)는 내부에서 외부 변수를 사용하지 않으므로 클로저가 없지만, 콜백 함수 (B)에서는 fruit이라는 외부 변수를 참조하고 있으므로 클로저가 존재합니다.

 

정리

클로저란 어떤 함수에서 선언한 변수를 참조하는 내부함수를 외부로 전달할 경우, 함수의 실행이 종료된 후에도 해당 변수가 사라지지 않는 현상입니다.

 

내부함수를 외부로 전달하는 방법은 함수를 return 하는 경우뿐 아니라 콜백으로 전달하는 경우도 포함됩니다.

 

코어 자바스크립트: 핵심 개념과 동작 원리로 이해하는 자바스크립트 프로그래밍을

공부하며 이해한 내용을 기반으로 정리한 게시글입니다.

'코드 > JavaScript' 카테고리의 다른 글

자바스크립트 클래스  (0) 2024.05.29
자바스크립트 프로토타입  (0) 2024.05.24
콜백함수 (Callback)  (0) 2024.05.17
undefined와 null의 차이  (0) 2024.05.17
얕은 복사와 깊은 복사  (0) 2024.05.17