JavaScript - 클로저
자바스크립트 - 클로저
클로저의 의미 및 원리 이해
클로저(Closure)
: 여러 함수형 프로그래밍 언어에서 등장하는 보편적인 특성- MDN에서는 클로저에 대해 클로저는 함수와 그 함수가 선언될 당시의 lexical environment의 상호관계에 따른 현상”이라 설명
- 여기서 Lexical Environment는 실행 컨텍스트의 구성 요소 중 하나인
outerEnvironmentReference
에 해당 - 내부함수에서 외부 변수를 참조하는 경우인 combination
- 즉, 선언될 당시의 LexicalEnvironment와의 상호환계를 의미
외부함수의 변수를 참조하는 내부 함수
var outer = function () {
var a = 1;
var inner = function () {
console.log(++a);
};
inner();
};
outer();
- outer 함수에서 변수a를 선언, outer의 내부함수인 inner 함수에서 a의 값을 1만큼 증가시킨 다음 출력
- inner 함수 내부에서는 a를 선언하지 않았기 때문에 environmentRecord에서 값을 찾지 못하므로 outerEnviromentRefernce에 지정된 상위 컨텍스트인 outer의 LexicalEnvironment에 접근해서 다시 a 찾음
- outer함수의 실행 컨텍스트가 종료되면 LexicalEnvironment에 저장된 식별자들(a, inner)에 대한 참조 삭제
- 각 주소에 저장돼 있던 값들은 자신을 참조하는 변수가 하나도 없게 되므로 가비지 컬렉터의 수집 대상
외부함수의 변수를 참조하는 내부 함수(2)
var outer = function () {
var a = 1;
var inner = function () {
return ++a;
};
return inner();
};
var outer2 = outer();
console.log(outer2);
- inner 함수 내부에서 외부변수인 a 사용
- inner 함수를 실행한 결과를 리턴, 결과적으로 outer 함수의 실행 컨텍스트가 종료된 시점에는 a 변수를 참조하는 대상 ❌
- a, inner 변수의 값들은 언젠가 가비지 컬렉터에 의해 소멸
외부 함수의 변수를 참조하는 내부 함수(3)
- outer의 실행 컨텍스트가 종료된 후에도 inner함수를 호출할 수 있게 만들기
var outer = function () {
var a = 1;
var inner = function () {
return ++a;
};
return inner;
};
var outer2 = outer();
console.log(outer2()); //2
console.log(outer2()); //3
1.
- inner 함수의 실행 결과가 아닌 inner 함수 자체를 반환
- outer 함수의 실행 컨텍스트가 종료될 때 outer2 변수는 outer의 실행 결과인 inner 함수를 참조
- outer2 를 호출하면 앞서 반환된 inner 함수 실행
2.
- inner 함수의 실행 컨텍스트의 environmentRecord에는 수집할 정보 ❌
- outer-EnvironmentReference에는 inner 함수가 선언된 위치의 L.E가 참조 복사
- inner 함수는 outer 함수 내부에서 선언, outer 함수의 L.E가 담길 것
- 스코프 체이닝에 따라 outer에서 선언한 변수a에 접근해서 1만큼 증가시킨 후 그 값이 2를 반환, inner 함수의 실행 컨텍스트 종료
3.
- inner 함수의 실행 시점에는 outer함수가 이미 실행 종료됐음에도 불구하고 변수a에 접근
- 왜? 이는 가비지 컬렉터의 동작방식 때문
- 가비지 컬렉터는 어떤 값을 참조하는 변수가 하나라도 있다면 그 값은 수집 대상 포함 ❌
- 외부함수인 outer의 실행이 종료되더라도 내부 함수인 inner함수의 실행 컨텍스크가 활성화되면 outerEnvironmentRefernce가 outer 함수의 L.E를 필요로 할 것이므로 수집 대상에서 제외
- 그 덕에 inner 함수가 변수에 접근 가능
💡 클로저란, 어떤 함수 A에서 선언한 변수a를 참조하는 내부 함수 B를 외부로 전달할 경우 A의 실행 컨텍스트가 종료된 이후에도 변수 a가 사라지지 않는 현상
클로저와 메모리 관리
- 클로저는 객체지향과 함수형 모두를 아우르는 매우 중요한 개념
- 메모리 소모는 클로저의 본질적인 특성
메모리 관리 방법
- 클로저는 어떤 필요에 의해 의도적으로 함수의 지역변수를 메모리를 소모하도록 함으로써 발행 ➡️ 그 필요성이 사라진 시점에는 더는 메모리를 소모하지 않게 해주면 됨
- 즉,
참조카운터를 0
으로 하면 된다. - 참조 카운터를 0으로 만드는 방법
- 식별자에 참조형이 아닌 기본형 데이터(보통 null 이나 undefined)를 할당
(1) 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) 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);
})();
(3) eventListener에 의한 클로저의 메모리 해제
(function () {
var count = 0;
var button = document.createElement("button");
button.innerText = "click";
var clickHandler = function () {
console.log(++count, "times clicked");
if (count >= 10) {
button.removeEventListener("click", clickHandler);
clickHandler = null; //clickHandler 식별자의 함수 참조를 끊음
}
};
button.addEventListener("click", clickHandler);
document.body.appendChild(button);
})();
클로저 활용 사례
접근 권한 제어(정보 은닉)
정보 은닉
: 어떤 모듈의 내부 로직에 대해 외부로의ㅣ 노출을 최소하해서 모듈간의 결합도를 낮추고 유연성을 높이고자 하는 현대 프로그래밍 언어의 중요한 개념- 접근 권한 : public, private, protected
- public : 외부에서 접근 가능한 것
- private : 내부에서만 사용하며 외부에 노출되지 않는 것
부분 적용 함수
부분 적용 함수
: n개의 인자를 받는 함수에 미리 m개의 인자만 넘겨 기억시켰다가, 나중에 (n -m)개의 인자를 넘기면 비로소 원래 함수의 실행 결과를 얻을 수 있게끔 하는 함수- 디바운스 : 짧은 시간 동안 동일한 이벤트가 많이 발생할 경우 이를 전부 처리하지 않고 처음 또는 마지막에 발생한 이벤트에 대해 한번만 처리 하는 것(프론트 엔드 성능 최적화에 큰 도움을 주는 기능 중 하나)
- scroll, wheel, mousemove,resize 등 적용
커링 함수
커링 함수
: 여러 개의 인자를 받는 함수를 하나의 인자만 받는 함수로 나눠서 순차적으로 호출될 수 있게 체인 형태로 구성한 것- 한 번에 하나의 인자만 전달하는 것을 원칙
- 중간 과정 상의 함수를 실행한 결과는 그 다음 인자를 받기 위해 대기만 할 뿐, 마지막 인자가 전달되기 전까지는 원본 함수가 실행되지 않음
- 다만, 인자가 많아질수록 가독성이 떨어짐
참고 : 코어 자바스크립트(Core JavaScript), 정재남