[코어 자바스크립트] 클로저① - 클로저의 의미 및 원리 이해

2021. 6. 27. 17:01개발공부/자바스크립트

클로저란? 

클로저에대한 정의는 다양하지만 일반적인 정의는 다음과 같다.

"A closure is the combination of a function and the lexical environment within which that function was declared."

여기서 'combination'은 내부함수에서 외부 변수를 참조하는 경우에 한해서만 combination, 즉 '선언될 당시의 Lexical Environment와의 상호관계'가 의미가 있다.

-> 어떤 함수에서 선언한 변수를 참조하는 내부함수에서만 발생하는 현상

-> 외부함수가 더이상 사용되지않는 경우도, 내부함수가 외부함수에 접근 할 수 있다.

 

// 클로저가 아닌 예제코드1

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

// 결과
>> 2
  1. 내부함수에서 ++a를 콘솔로 찍어주는데, a는 외부에서 선언되었지만 결과값은 잘 찍히고있다.
  2. inner함수 내부인 environmentRecord에서 값을 찾지 못하므로 스코프체인이 실행되면서 외부 함수 outer의 LexicalEnvironment에 접근해 다시 a를 찾는다. 여기서 2가 출력된다. 
  3. outer함수의 실행 컨텍스트가 종료되면 LexicalEnvironment에 저장된 식별자들(a, inner)에 대한 참조는 더이상 필요가없기때문에 삭제된다. 각 주소에 저장돼 있던 값들은 garbage collector의 수집 대상이 된다.
// 클로저가 아닌 예제코드2

var outer = function () {
	var a = 1;
    	var inner = function () {
    		return ++a;
    };
    return inner ();
};
var outer2 = outer();
console.log(outer2);

// 결과
>> 2
  1. 1번 예제코드와 마찬가지로 inner함수 내부에서 외부변수인 a를 사용하였다.
  2. 6번째 줄에서 inner함수를 실행한 결과를 리턴하고있으니 outer함수의 실행 컨텍스트가 종료된 시점에는 a변수를 참조하는 대상은 사라진다.
  3. 마찬가지로 a, inner변수의 값들은 garbage collector에 의해 소멸된다.

🍒여기서 잠깐!!!

garbage collector가 정확히 무엇일까?

garbage collection(가비지 컬렉션)은 자동 메모리 관리 형식이다.

자바스크립트는 객체가 생성될때 필요 여부에따라 자동으로 메모리를 할당, 해제한다. 이러한 기능이 가비지 컬렉션(Garbage Collection)이다.

목적은 "메모리 할당을 모니터링하고 메모리의 블록이 더 이상 필요하지 않은 시점을 확인하여 회수하는 것"이다. (참조블로그1, 2)

가비지 컬렉션 알고리즘에도 종류가있는데, 위의 코드들에서 변수의 값들이 garbage collector에 의해 소멸되는 것과같은 Reference-counting가비지 컬렉션이있다. 이 알고리즘은 특정 객체를 참조하는 객체가 없다면, 그 객체는 가비지 컬렉션에의해 회수된다.

 

 

이제 그래서 클로저가 뭔지 클로저 예제코드를 살펴보자!

outer의 실행 컨텍스트가 종료되었는데 inner 함수를 호출하면 어떻게될까?

var outer = function () {
	var a = 1;
    	var inner = function () {
    	return ++a;
    };
    return inner;
};
var outer2 = outer(); // outer 함수의 실행 컨텍스트 종료될때 outer2변수는 outer의 실행 결과인 inner함수 참조
console.log(outer2()); // outer2 호출, inner함수 실행
// 결과
>> 2
console.log(outer2());
// 결과
>> 3

클로저가 아닌 예제코드와 차이점은 inner함수의 실행 결과()가 아닌 inner 함수 자체를 반환했다.

outer함수의 LexicalEnvironment가 참조복사되어 a의 값을 가져온다.

여기서, inner함수의 실행 시점에 outer함수는 이미 실행이 종료되었는데, outer의 LexicalEnvironment에 어떻게 접근 할 수 있는걸까?

💡가비지 컬렉터의 동작방식
      가비지 컬렉터는 어떤 값을 참조하는 변수가 하나라도 있다면 그 값은 수집 대상에 포함시키지 않는다.
💡클로저의 정의
     "클로저란 어떤 함수 A에서 선언한 변수 a를 참조하는 내부함수 B를 외부로 전달할 경우 A의 실행 컨텍스트가 종료된 이후에도    변수 a가 사라지지 않는 현상"

여기서 주의할 점은 '외부로 전달'이 return만을 의미하는 것은 아니라고한다. return 없이도 setInterval, setTimeout과 같은 메서드를 사용했을때 처럼 클로저가 발생하는 다양한 경우가있다.

// setInterval/setTimeout

(function () {
	var a = 0;
    	var intervalId = null;
    	var inner = function () {
    	if (++a >= 10) {
        	clearInterval(intervalId); 
            // clearInterval메서드는 setInterval로 반복하고 있는 걸 멈추게한다
        }
        
        console.log(a);
    };
    intervalId = setInterval(inner, 1000);
    // 외부객체인 window의 메서드에 전달할 콜백 함수 내부에서 지역변수를 참조
})();

// 결과
>> 1 2 3 4 5 6 7 8 9 10

 

  • 익명 함수의 실행컨텍스트가 종료된 이후에도 setInterval에 전달할 inner(콜백)함수 내부에서 외부함수의 지역변수를 참조한다.
// eventListener

(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);
})();
  • 클릭 이벤트 발생할때마다 콜백함수가 실행되는데, 그때 외부함수의 지역변수를 참조한다.

 

클로저와 메모리관리

메모리 누수의 이유로 클로저 사용을 지양해야한다는 얘기가많다.

메모리 누수는 의도치않게 어떤 값의 참조 카운트가 0이 되지 않아 GC(garbage collector)의 수거 대상이 되지않은 경우에 일어난다.

하지만, 이런 메모리 누수는 최근의 자바스크립트 엔진에서는 거의 발생하지않는다.

개발자는 '메모리 소모'에 대한 관리법만 잘 파악하면 된다고한다.

-> 지역변수의 필요성이 사라졌다면 참조 카운트를 0으로 만들어주면된다.

그렇다면, 참조 카운트를 0으로 만들어주는 방법은?

식별자에 참조형이 아닌 기본형 데이터(보통 null이나 undefiend)를 할당하면 된다.

// return에 의한 클로저의 메모리 해제

var outer = (function () {
	var a = 1;
    	var inner = function () {
    		return ++a;
    };
    return inner;
})();
console.log(outer());
console.log(outer());
outer = null; // outer 식별자의 inner 함수 참조를 끊음

// 결과
>> 2 3 null
// 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);
})();

 

 

클로저 관련 면접 질문

1. 클로저는 무엇이며, 왜 사용하는가?

- 클로저는 어떤 함수에서 선언한 변수를 참조하는 내부함수에서만 발생하는 현상인데, 이때  외부함수가 더이상 사용되지않는 경우도, 내부함수가 외부함수에 접근 할 수 있다.

- 클로저란 어떤 함수 A에서 선언한 변수 a를 참조하는 내부함수 B를 외부로 전달할 경우 A의 실행 컨텍스트가 종료된 이후에도    변수 a가 사라지지 않는 현상

클로저를 사용하여 상태유지, 전역 변수의 사용억제, 정보의 캡슐/은닉화가 가능하다.

-> 클로저는 현재상태를 기억하고 변경된 최신상태를 유지시켜준다.

-> 전역변수를 사용하면 의도치않게 값이 변경될 수 있다. 전역변수대신 지역변수를 사용하고, 이때 클로저를 사용하여 이전 상태를 기억할 수 있다.

-> 캡슐화와 은닉화를 사용해 전역스코프를 더럽히지않고 모듈화가 가능하다.

참고블로그

 

 

++ 프론트엔드 관련 면접 질문

1. Virtual DOM이 뭔지 아시는지? 썼을 때의 장점?

가상의 돔, 변화가 일어나면 브라우저(돔)에 새로운 것을 넣는것이 아니라 자바스크립트로 이루어진 가상의 돔에 렌더링 후 기존의 돔과 비교 후 변화가 필요한 곳에만 업데이트 하는 방식. 따라서, dom처리횟수를 최소화 할 수 있고 브라우저의 렌더링 횟수를 줄여줘 효율성을 높여준다.

 

 

 

출처: 정재남, 코어 자바스크립트, 위키북스(2019), 5장 클로저