클로저 개념 및 동작 원리
클로저
는 자바스크립트 고유의 개념이 아니라 함수를 일급 객체로 취급하는 함수형 프로그래밍 언어
에서 사용되는 중요한 특성이다. MDN에서는 클로저를 아래와 같이 정의한다.클로저는 함수와 그 함수가 선언됐을 때의 렉시컬 환경과의 조합이다.
여기서 중요한 키워드는 “함수가 선언됐을 때의 렉시컬 환경(Lexical environment)”이다.
예시를 통해서 이해해보자
function outerFunc() {
var x = 10;
var innerFunc = function () { console.log(x); };
innerFunc(); // 함수를 호출
}
outerFunc(); // 10
함수 outerFunc 안에서 내부함수 innerFunc가 선언되고 호출되었다. 이때 내부함수 innerFunc는 자신을 포함하고 있는 외부함수 outerFunc의 변수 x에 접근할 수 있다. 이는 함수 innerFunc가 함수 outerFunc의 내부에 선언되었기 때문이다.
스코프는 함수를 호출할 때가 아니라 함수를 어디에 선언하였는지에 따라 결정된다. 이를
렉시컬 스코핑
이라 한다. - innerFunc 함수 스코프 내에서 변수 x를 검색한다. 검색이 실패하였다.
- innerFunc 함수를 포함하는 외부 함수 outerFunc의 스코프에서 변수 x를 검색한다. 검색이 성공하였다.
이번에는 내부함수 innerFunc를 호출이 아닌 반환하도록 변경해보자.
function outerFunc() {
var x = 10;
var innerFunc = function () { console.log(x); };
return innerFunc; // 함수를 반환
}
/**
* 함수 outerFunc를 호출하면 내부 함수 innerFunc가 반환된다.
* 그리고 함수 outerFunc의 실행 컨텍스트는 소멸한다.
*/
var inner = outerFunc(); // outerFunc 실행과 동시에 innerFunc 반환
inner(); // 10
함수 outerFunc는 실행된 이후 콜스택(실행 컨텍스트 스택)에서 제거되었으므로 함수 outerFunc의 변수 x 또한 더이상 유효하지 않게 되어 변수 x에 접근할 수 있는 방법은 달리 없어 보인다.
그러나 위 코드의 실행 결과는 변수 x의 값인 10이다. 자신을 포함하고 있는 외부함수보다 내부함수가 더 오래 유지되는 경우, 외부 함수 밖에서 내부함수가 호출되더라도 외부함수의 지역 변수에 접근할 수 있다. 이러한 함수를 클로저(Closure)라고 부른다.
위에서 정의했던 내용을 재정의하면 클로저는 반환된 내부함수가 자신이 선언됐을 때의 환경의 스코프를 기억하여 자신이 선언됐을 때의 환경 밖에서 호출되어도 그 환경에 접근할 수 있는 함수라고 할 수 있다.
내부함수 실행 컨텍스트 내의 활성 객체(변수, 함수 선언 등의 정보를 가지고 있는 객체)에서 외부함수를 참조하는 한 외부함수는 유효하여 내부함수가 참조할 수 있다.
클로저 사용 예시
클로저가 가장 유용하게 사용되는 상황은 현재 상태를 기억하고 변경된 최신 상태를 유지하는 것이다. 아래 예제를 살펴보자.
<!DOCTYPE html>
<html>
<body>
<button class="toggle">toggle</button>
<div class="box" style="width: 100px; height: 100px; background: red;"></div>
<script>
const toggleBtn = document.querySelector('.toggle');
const box = document.querySelector('.box');
const toggle = (function () {
let isShow = false;
// 1. 클로저를 반환
return function () {
box.style.display = isShow ? 'block' : 'none';
// 3. 상태 변경
isShow = !isShow;
};
})();
// 2. 이벤트 프로퍼티에 클로저를 할당
toggleBtn.onclick = toggle;
</script>
</body>
</html>
- 즉시실행함수는 함수를 반환하고 즉시 소멸한다. 즉시실행함수가 반환한 함수는 자신이 생성됐을 때의 렉시컬 환경(Lexical environment)에 속한 변수 isShow를 기억하는 클로저다.
- 클로저를 버튼이 클릭될 때마다 실행할 함수로서 할당했다. 클로저를 제거하지 않는 한 클로저가 기억하는 렉시컬 환경의 변수 isShow는 소멸하지 않는다.
- 버튼을 클릭하면 이벤트 프로퍼티에 할당한 이벤트 핸들러인 클로저가 호출된다. 이때 .box 요소의 표시 상태를 나타내는 변수 isShow의 값이 변경된다. 변수 isShow는 클로저에 의해 참조되고 있기 때문에 유효하다.
클로저의 장단점
- 장점
- 정보 은닉: 클로저를 사용하면 외부에서 접근할 수 없는 private 변수를 생성하고, 해당 변수에 접근할 수 있는 getter와 setter 함수를 반환하는 형태로 구현할 수 있다. 이를 통해 데이터를 보호하고 캡슐화할 수 있다.
function createPerson(name) {
let age = 0;
return {
getName: function() {
return name;
},
getAge: function() {
return age;
},
setAge: function(newAge) {
age = newAge;
}
};
}
const person = createPerson('John');
console.log(person.getName());// "John" 출력
console.log(person.getAge());// 0 출력
person.setAge(30);
console.log(person.getAge());// 30 출력
function greet(name) {
setTimeout(function() {
console.log(`Hello, ${name}!`);
}, 1000);
}
greet('John');// 1초 후에 "Hello, John!" 출력
- 단점
- 메모리 누수: 클로저는 외부 함수의 변수를 참조하므로, 클로저가 참조하는 변수가 메모리에 유지된다. 이는 사용하지 않는 변수에 대한 참조를 제거하지 않을 경우 메모리 누수가 발생할 수 있다. 따라서, 클로저를 사용할 때는 필요한 변수만 유지하고 사용하지 않는 변수는 적절히 해제해야 한다.
function outerFunction() {
let outerVariable = 'Outer';
function innerFunction() {
console.log(outerVariable);
}
outerVariable = null;// 클로저에 의해 참조되는 변수에 대한 할당 해제
return innerFunction;
}
const closure = outerFunction();
closure();// null 출력
Share article