728x90
728x90

 

 

01 클로저의 의미 및 원리 이해

 

클로저 closure :

여러 함수형 프로그래밍 언어에서 등장하는 보편적인 특성으로 자바스크립트 고유의 개념이 아니라서 ECMAScript 명세에서도 정의를 다루지 않고 있고 문헌마다 다양하게 정의, 설명하고 있으며 문장도 모호하다. 그러나 반대로 잘 잡으면 이해하기 쉬운 개념이 클로저

문헌들에서는 자신을 내포하는 함수의 컨텍스트에 접근할 수 있는 함수, 특정 스코프에 접근할 수 있도록 의도적으로 스코프에서 함수를 정의하기, 함수를 선언할 때 만들어지는 유효범위가 사라진 후에도 호출할 수 있는 함수, 이미 생명 주기상 끝난 외부 함수의 변수를 참조하는 함수, 로컬 변수를 참조하고 있는 함수 내의 함수 등등으로 얘기한다.

 

함수를 둘러싼 환경이라는 것이 렉시컬 스코프이다.
함수를 만들고 그 함수 내부의 코드가 탐색하는 스코프를 함수 생성 당시의 렉시컬 스코프로 고정하면 바로 클로저가 되는 것이다.

 

 

클로저란 "어떤 함수에서 선언한 변수를 참조하는 내부함수에서만 발생하는 현상"

MDN (Mozilla Developer Network) 에서는 클로저를 'the combination of a function and the lexical environment within which that function was declared' 라고 한다. 즉, 함수와 함수가 선언될 당시의 lexical environment의 상호관계에 따른 현상인 것이다.
선언 당시의 lexical environment는 2장에 나온 실행컨텍스트의 구성 요소 중 하나인 outerEnvironmentReference에 해당

LexicalEnvironment의 environmentRecordouterEnvironmentReference에 의해
변수의 유효범위인 스코프가 결정되고 스코프체인이 가능해진다.

# 어떤 컨텍스트 A에서 선언한 내부함수 B의 실행컨텍스트가 활성화된 시점에는
B의 outerEnvironmentReference가 참조하는 대상인 A의 LexicalEnvironment에도 접근이 가능하고
A에서는 B에서 선언한 변수에 접근할 수 없지만 반대로는 가능한데,
내부함수 B가 A의 LexicalEnvironment를 언제나 사용하진 않고
그럴 경우 combination이라 할 수 없다

즉 선언 당시의 LecxicalEnvironment와의 상호관계가 의미가 있다

 

이제 외부 함수의 변수를 참조하는 내부 함수의 4가지 예시로 살펴보겠습니다.

 

5-1

var outer = function() {
	var a = 1;
	var inner = function() {
		console.log(++a);  // 2 출력
	// a를 선언하지 않아 environmentRecord에서 값을 못찾아서
	// LexicalEnvironment에 접근해 다시 a를 찾는다
	};
	inner();
);
outer(); 
// 실행 컨텍스트가 종료되면 LexicalEnvironment에 저장된
// 식별자 (a, inner) 에 대한 참조를 지운다
// 이제 각 주소에 저장된 값들은 참조당하는 변수가 없어서 
// 언젠가 가비지컬렉터에 수거가 될 것이다

 

5-2

var outer = function() {
	var a = 1;
    
	var inner = function() {
		return ++a;  // 외부변수 a 사용
	};
	return inner(); // inner함수의 실행결과 리턴
};

var outer2 = outer();  
console.log(outer2); // 2이지만 예제는 실행되지 않았다...
// outer함수의 실행컨텍스트가 종료된 시점에는 a변수를 참조하는 대상의 소멸
// 가비지컬렉터에 의해 소멸되며 일반적 함수, 내부함수의 동작과 같다

 

5-1, 5-2는 outer 함수의 실행컨텍스트가 종료되기 이전에 inner 함수의 실행컨텍스트가 종료되며 이후 별도로 inner 함수를 호출 할 수 없는 예제이고 5-3은 outer 함수의 실행컨텍스트가 종료 이후 inner 함수를 호출할 수 있게 만든 예제입니다.

 

5-3

var outer = function() {
	var a = 1;
    
	var inner = function() {
		return ++a;  
	};
	return inner; 
    // 실행결과가 아닌 함수 자체를 반환
};

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

 

inner  함수의 실행컨텍스트의 environmentRecord에는 수집할 정보가 없으며
outer-environmentReference에는 inner 함수가 선언된 위치의 LexicalEnvironment가 참조 복사되며
inner 함수는 outer 함수 내부에서 선언되어 outer 함수의 LexicalEnvironment가 담기고
스코프 체이닝에 따라 outer 에서 선언한 변수 a 에 접근해서 증가시킨 후 2를 반환하고 inner 함수의 실행컨텍스트가 종료된다
다시 마지막에서 outer2를 호출하면 a를 다시 증가시키고 3을 반환한다

inner 함수의 실행시점에는  outer 함수가 실행종료됐는데 outer 함수의 LexicalEnvironment에 접근할 수 있는 이유는
어떤 값을 참조하는 변수가 하나라도 있으면 수집대상이 니게 되는 가비지컬렉터의 동작 특성 때문
즉 예제
1,2와 다르게 outer 외부함수의 실행이 종료되도 내부함수인 inner 함수는 언젠가 outer2를 호출해 실행할 가능성이 예측되며
inner 함수의 실행 컨텍스트가 활성화되면 outerEnvironmentReference가 outer 함수의 LexicalEnvironment를 필요로 할 것이기 때문
그래서 inner 함수가 변수에 접근 가능한 것

= 클로저는 어떤 함수에서 선언한 내부 함수에서만 발생하는 현상이자
유일하게 함수의 실행컨텍스트가 종료된 후에도 외부함수의 LexicalEnvironment가 가비지컬렉팅되지 않는 현상

 

외부로의 전달은 return 없이도 가능하다

 

(function() {
	var a = 0;
	var intervalId = null;
	var inner = function(){
		if(++a>=10){
			clearInterval(intervalId);
		}
		console.log(a);  // 1 2 3 4 ... 10
	};
	intervalId = setInterval(inner, 1000);
})();

 

즉시실행함수의 예인데 별도의 외부객체인 window의 메서드에 전달할 콜백함수 내부에서 지역변수를 참조한다
지역변수를 참조하는 내부함수를 외부에 전달했기에 클로저라 할 수 있다

 

더보기
var color = 'red';
function foo() {
    var color = 'blue'; // 2
    function bar() {
        console.log(color); // 1
    }
    return bar;
}
var baz = foo(); // 3
baz(); // 4
  1. bar는 color를 찾아 출력하는 함수로 정의되었다.
  2. 그리고 bar는 outer environment 참조로 foo의 environment를 저장하였다.
  3. bar를 global의 baz란 이름으로 데려왔다.
  4. global에서 baz(=bar)를 호출했다.
  5. bar는 자신의 스코프에서 color를 찾는다.
  6. 없다. 자신의 outer environment 참조를 찾아간다.
  7. outer environment인 foo의 스코프를 뒤진다. color를 찾았다. 값은 blue이다.
  8. 때문에 당연히 blue가 출력된다.

이런 bar(또는 baz)와 같은 함수를 우리는 클로저라고 부른다. 
baz bar로 초기화할 때는 이미 bar outer lexical environment foo로 결정한 이후이다. 
때문에, 
bar의 생성과 직접적인 관련이 없는 global에서 아무리 호출하더라도 여전히 foo에서 color를 찾는 것이다. 

foo의 렉시컬환경 인스턴스는 foo(); 수행이 끝난 이후 GC가 회수해야 하는데 사실을 그렇지 않다. 
bar는 여전히 바깥 렉시컬 환경인 foo의 렉시컬 환경을 계속 참조하고 있고,
 
bar baz가 여전히 참조하고 있기 때문이다.(baz(=bar) -> foo)

 

 

 

 

 

 

02 클로저와 메모리 관리

 

728x90
728x90
블로그 이미지

coding-restaurant

코딩 맛집에 방문해주셔서 감사합니다.

,

v