자바스크립트에서 클로저(Closure)는 함수와 그 함수가 선언된 렉시컬 환경의 조합을 말합니다. 클로저를 통해 함수는 자신이 선언된 환경을 기억하고, 그 환경에 접근할 수 있습니다. 클로저는 자바스크립트의 강력한 기능 중 하나로, 함수형 프로그래밍 패턴에서 자주 사용되며, 데이터 은닉과 같은 다양한 응용이 가능합니다.
1. 클로저의 기본 개념
클로저는 함수가 자신이 선언된 환경을 기억하여, 함수가 호출되는 시점이 아닌 선언되는 시점의 환경에 접근할 수 있게 합니다.
function outerFunction() {
const outerVariable = 'I am outside!';
function innerFunction() {
console.log(outerVariable);
}
return innerFunction;
}
const closureFunction = outerFunction();
closureFunction(); // I am outside!
위 예제에서 innerFunction
은 outerVariable
을 참조합니다. outerFunction
이 호출된 후에도 innerFunction
은 outerVariable
에 접근할 수 있습니다. 이는 innerFunction
이 outerVariable
을 포함한 환경을 기억하고 있기 때문입니다.
2. 클로저의 동작 원리
클로저는 다음과 같은 원리로 동작합니다.
- 함수 생성: 함수가 생성될 때, 함수의 스코프 체인이 생성됩니다.
- 렉시컬 환경: 함수가 생성된 위치의 렉시컬 환경이 함수에 저장됩니다.
- 변수 접근: 함수가 호출될 때, 저장된 렉시컬 환경을 통해 변수에 접근할 수 있습니다.
3. 클로저의 응용
클로저는 다양한 용도로 사용될 수 있습니다.
3.1 데이터 은닉
클로저를 사용하여 데이터 은닉을 구현할 수 있습니다. 이는 객체 지향 프로그래밍에서의 캡슐화를 구현하는 방식과 유사합니다.
function createCounter() {
let count = 0;
return {
increment() {
count++;
console.log(count);
},
decrement() {
count--;
console.log(count);
},
getCount() {
return count;
}
};
}
const counter = createCounter();
counter.increment(); // 1
counter.increment(); // 2
counter.decrement(); // 1
console.log(counter.getCount()); // 1
위 예제에서 count
변수는 createCounter
함수 내부에서만 접근할 수 있으며, 외부에서는 접근할 수 없습니다. 이는 createCounter
함수가 반환하는 객체의 메서드를 통해서만 count
변수에 접근할 수 있게 합니다.
3.2 함수 팩토리
클로저를 사용하여 함수 팩토리를 만들 수 있습니다. 이는 특정 설정이나 상태를 유지하는 함수들을 생성할 때 유용합니다.
function createGreeter(greeting) {
return function(name) {
console.log(`${greeting}, ${name}!`);
};
}
const sayHello = createGreeter('Hello');
const sayHi = createGreeter('Hi');
sayHello('Alice'); // Hello, Alice!
sayHello('Bob'); // Hello, Bob!
sayHi('Charlie'); // Hi, Charlie!
위 예제에서 createGreeter
함수는 greeting
변수를 기억하는 클로저를 반환합니다. 각각의 클로저는 서로 다른 greeting
값을 유지합니다.
3.3 반복문과 클로저
클로저를 사용하여 반복문 내부의 비동기 처리를 올바르게 수행할 수 있습니다.
for (var i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
// 3, 3, 3 (1초 후에 각각 출력)
위 예제에서는 var
로 선언된 i
변수가 반복문이 끝난 후의 값을 참조합니다. 이를 해결하기 위해 클로저를 사용할 수 있습니다.
for (let i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
// 0, 1, 2 (1초 후에 각각 출력)
또는 IIFE
(Immediately Invoked Function Expression)를 사용할 수 있습니다.
for (var i = 0; i < 3; i++) {
(function(i) {
setTimeout(function() {
console.log(i);
}, 1000);
})(i);
}
// 0, 1, 2 (1초 후에 각각 출력)
위 예제에서 IIFE
는 각 반복마다 i
값을 인자로 받아 클로저를 생성합니다. 이 클로저는 각 i
값을 별도의 렉시컬 환경에 저장하여 올바른 값을 출력합니다.
4. 클로저의 메모리 관리
클로저는 강력하지만, 메모리 관리에 주의해야 합니다. 클로저가 참조하는 변수가 많아질수록 메모리 사용량이 증가할 수 있습니다. 따라서 불필요한 클로저는 생성하지 않도록 주의해야 합니다.
function heavyClosure() {
const largeArray = new Array(1000000).fill('data');
return function() {
console.log(largeArray[0]);
};
}
const heavy = heavyClosure();
// 클로저가 largeArray를 계속 참조하여 메모리 사용량 증가
위 예제에서 heavyClosure
함수는 큰 배열을 클로저로 유지하여 메모리를 많이 사용합니다. 불필요한 클로저는 사용 후에 참조를 제거하여 메모리 누수를 방지해야 합니다.
function createCounter() {
let count = 0;
function increment() {
count++;
console.log(count);
}
function decrement() {
count--;
console.log(count);
}
function getCount() {
return count;
}
return { increment, decrement, getCount };
}
const counter = createCounter();
counter.increment(); // 1
counter.increment(); // 2
counter.decrement(); // 1
console.log(counter.getCount()); // 1
5. 클로저와 this 키워드
클로저와 this
키워드의 동작을 이해하는 것도 중요합니다. 클로저 내부에서 this
키워드를 사용할 때, 클로저가 생성된 위치의 this
를 참조합니다.
const obj = {
value: 10,
createFunction: function() {
return function() {
console.log(this.value);
};
}
};
const func = obj.createFunction();
func(); // undefined (전역 객체의 this 참조)
const boundFunc = obj.createFunction().bind(obj);
boundFunc(); // 10 (obj의 this 참조)
위 예제에서 createFunction
이 반환하는 클로저는 this
를 전역 객체로 참조합니다. bind
메서드를 사용하여 this
를 obj
로 바인딩할 수 있습니다.
6. 클로저의 한계와 주의점
클로저는 매우 유용하지만, 몇 가지 한계와 주의점이 있습니다.
- 메모리 누수: 클로저가 참조하는 변수가 많아질수록 메모리 사용량이 증가할 수 있습니다.
- 디버깅 어려움: 클로저를 사용한 코드의 디버깅이 어려울 수 있습니다.
- 성능 문제: 불필요한 클로저 생성은 성능 문제를 일으킬 수 있습니다.
클로저는 자바스크립트의 중요한 개념으로, 이를 잘 활용하면 코드의 재사용성과 유지보수성을 크게 향상시킬 수 있습니다. 클로저를 올바르게 이해하고 사용하면 더욱 효율적이고 강력한 자바스크립트 코드를 작성할 수 있습니다.