View

클로저란?

MDN에서 정의하는 클로저를 직역하면 "클로저는 함수와 그 함수가 선언될 당시의 lexical environment의 상호관계에 따른 현상"입니다.

선언될 당시의 lexical environment란 실행 컨텍스트의 구성 요소 중 하나인 outerEnvironmentReference에 해당합니다. 

즉 클로저는 내부함수 B가 A의 LexicalEnvironment를 사용하는 경우, 내부함수에서 외부 변수를 사용한다면 클로저라는 현상이 발생할 수 있습니다.

 

예제 1.

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

inner 함수 내부에서는 a를 선언하지 않았기 때문에 enviornmentRecord에서 값을 찾지 못하므로 outerEnvironmentReference에 지정된 상위 컨텍스트인 outer의 LexicalEncironment에 접근해서 다시 a를 찾습니다. outer 함수의 실행 컨텍스트가 종료되면 L.E에 저장된 식별자들(a, inner)에 대한 참조를 지웁니다. 그러면 각 주소에 저장돼 있던 값들은 자신을 참조하는 변수가 하나도 없게 되므로 가비지 컬렉터의 수집 대상이 될 것입니다.

 

예제 2.

var outer = fucntion () {
	var a = 1;
   	var inner = function () {
    	retrun ++a;
    }
    return inner;
};

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

이번에는 outer 함수 마지막에 inner 함수 자체를 반환했습니다. 그러면 outer 함수의 실행 컨텍스트가 종료될 때 outer2 변수는 outer의 실행 결과인 inner 함수를 참조하게 될 것입니다. 이후 9번째에서 outer2를 호출하며 앞서 반환된 함수인 inner가 실행되겠죠.

inner 함수의 실행 시점에는 이미 outer 함수가 실행이 종료된 상태인데 어떻게 outer L.E에 접근할 수 있는 걸까요? 이는 가비지 컬렉터의 동작 방식 때문입니다. 가비지 컬렉터는 어떤 값을 참조하는 변수가 하나라도 있다면 그 값은 수집 대상에 포함시키지 않습니다. 외부 함수인 outer의 실행이 종료되더라도 내부 함수인 inner 함수는 언젠가 outer2를 실행함으로써 호출될 가능성이 열린 것입니다.

 

이처럼 함수의 실행 컨텍스트가 종료된 후에도 L.E가 가비지 컬렉터의 수집 대상에서 제외되는 경우는 지역변수를 참조하는 내부함수가 외부로 전달된 경우가 유일합니다. 

 

이를 바탕으로 클로저의 정의를 다시 내려보자면 클로저란 어떤 함수 A에서 선언한 변수 a를 참조하는 내부함수 B를 외부로 전달할 경우 A의 실행 컨텍스트가 종료된 이후에도 변수 a가 사라지지 않는 현상을 말합니다.

 

클로저 활용 사례

디바운스

https://webclub.tistory.com/607

만약 사용자가 스크롤한다고 가정합시다. 사용자가 아래로 5000픽셀 정도의 스크롤 다운을 하면 100개 이상의 이벤트가 짧은 시간 안에 발생하게 되죠.

따라서 디바운스는 짧은 시간 동안 동일한 이벤트가 많이 발생할 경우 이를 전부 처리하지 않고 처음 또는 마지막에 발생한 이벤트에 대해 한 번만 처리하는 것으로, 프론트엔드 성능 최적화에 큰 도움을 주는 기능입니다.

 

var debounce = function (eventName, func, wait) { // 이름, 실행할 함수, 마지막으로 발생한 이벤트인지 여부를 판단하기 위한 대기시간
	var timeoutId = null;
    return function (event) {
    	var self = this;
        console.log(eventName, 'event 발생');
        clearTimeout(timeoutId);
        timeoutId = setItmeout(func.bind(self, event), wait);
    }
}

var moveHandler = function (e) {
	console.log('move event 처리');
}

document.body.addEventListener('mousemove', debounce('move', moveHandler, 500));

내부에서는 timeoutId 변수를 생성하고, 클로저로 EventListener에 의해 호출될 함수를 반환합니다. 

반환될 한수 내부에서는 (return 뒤부터) setTimeout을 사용하기 위해 4번째 줄에서 this를 별도의 변수에 담고, 6번째 줄에서 대기큐를 초기화하게 했습니다. setTimeout으로 wait 시간만큼 지연시킨 다음 원래의 func을 호출합니다.

 

최초 event가 발생하면 7번째 줄에 의해 timeout의 대기열에 wait 시간 뒤에 func을 실행할 것이라는 내용이 담깁니다. 그런데 wait 시간이 경과하기 이전에 동일한 event가 발생하면 clearTimeout으로 대기열을 초기화하고, 새로운 대기열을 등록합니다. 마지막에 발생한 이벤트만이 초기화되지 않고 무사히 실행될 것입니다.

eventName, func, wait, timeout은 debounce의 변수이므로 return되어 eventHandler에 전달되는 함수에서 쓰일 때는 실행 컨텍스트가 죽은(?) 상황이므로 클로저로 처리되었다고 할 수 있습니다. 

Share Link
reply
«   2024/09   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30