
클로저는 자신의 스코프(Scope)와 함께 상위 스코프에 있는 변수에도 접근할 수 있는 함수를 말함.
자바스크립트의 중요한 특징 중 하나이며, 함수가 선언될 당시의 주변 환경(렉시컬 환경)을 기억하기 때문에 가능.
핵심: 함수가 함수 내부 변수뿐만 아니라 자신이 속한 외부 함수의 변수에도 접근할 수 있는 현상
렉시컬 환경은 실행 컨텍스트의 구성 요소 중 하나로, 식별자(변수, 함수 선언 등)와 그 식별자에 대한 바인딩 정보를 저장하는 환경.
쉽게 말해, 특정 스코프 내에서 어떤 변수와 함수에 접근할 수 있는지, 그리고 그 값은 무엇인지 등을 담고 있는 논리적인 구조
렉시컬 환경은 두 가지 주요 컴포넌트로 구성
클로저와 렉시컬 환경:
클로저가 중요한 이유는 함수가 생성될 때의 렉시컬 환경을 기억하기 때문.
내부 함수가 외부 함수의 변수를 참조하면, 자바스크립트 엔진은 내부 함수의 렉시컬 환경의 외부 환경 참조를 통해 외부 함수의 렉시컬 환경까지 연결
외부 함수가 종료되더라도 내부 함수의 렉시컬 환경은 유지되며, 이 렉시컬 환경을 통해 외부 함수의 변수에 접근할 수 있게 되는 것
요약하면, 클로저는 함수가 자신의 렉시컬 환경(자신이 선언될 때의 주변 환경)을 기억하고, 이 환경 내의 변수들에 접근할 수 있는 능력.
렉시컬 환경은 스코프 내의 식별자와 그 바인딩 정보를 담는 구조이며, 클로저가 상위 스코프의 변수에 접근하는 메커니즘의 핵심적인 개념
자바스크립트는 렉시컬 스코프(Lexical Scope)라는 규칙을 따름. 이는 함수의 스코프가 함수가 선언될 때 결정된다는 의미.
함수가 호출되는 위치와는 상관없이, 함수가 어디에서 정의되었느냐에 따라 접근 수 있는 변수의 범위가 결정
함수가 실행되면 자신만의 실행 컨텍스트(Execution Context)를 생성.
이 실행 컨텍스트는 변수 객체(Variable Object), 스코프 체인(Scope Chain), this 등을 포함.
여기서 스코프 체인은 현재 실행 컨텍스트의 변수 객체와 상위 스코프의 변수 객체들을 연결한 리스트
클로저가 발생하는 상황은 다음과 같음
이때, 반환된 내부 함수는 여전히 자신이 선언될 당시의 렉시컬 환경(외부 함수의 스코프)을 기억하고 있으며, 그 스코프 내의 변수들에 접근할 수 있음.
외부 함수는 이미 실행이 끝났지만, 내부 함수(클로저)가 그 변수들을 참조하고 있기 때문에 가비지 컬렉터는 해당 변수들을 메모리에서 해제하지 않음.
1. 클로저 미사용 함수:
JavaScript
function countWithoutClosure() {
let count = 0;
return count;
}
console.log(countWithoutClosure()); // 0
console.log(count); // ReferenceError: count is not defined
countWithoutClosure 함수 내부에 선언된 count 변수는 함수 내부에서만 접근 가능 (렉시컬 스코프).return 문을 만나면, 함수 내부의 변수는 메모리에서 해제됨.count 변수에 접근할 수 없음.2. 클로저 활용 함수:
function countWithClosure() {
let count = 0;
return {
increase: function() {
count++;
return count;
},
decrease: function() {
count--;
return count;
},
getCount: function() {
return count;
},
};
}
const counter = countWithClosure();
console.log(counter.getCount()); // 0
counter.increase();
console.log(counter.getCount()); // 1
counter.decrease();
console.log(counter.getCount()); // 0
countWithClosure 함수는 객체를 반환.increase, decrease, getCount를 프로퍼티로 가지고 있음.countWithClosure의 스코프에 있는 count 변수를 참조.countWithClosure 함수가 실행 완료된 후에도, 반환된 객체의 메서드(increase, decrease, getCount)는 여전히 count 변수에 접근할 수 있음.count 변수에 대한 클로저를 형성했기 때문.왜 함수 안에서 또 함수를 return 하는가?
외부 함수의 내부 상태(count 변수)를 외부 스코프에서 직접 접근하는 것은 렉시컬 스코프 규칙상 불가능
내부 함수를 반환함으로써, 이 내부 함수를 통해 외부 함수의 변수에 간접적으로 접근하고 조작할 수 있게 되는 것.
클로저 함수를 호출해서 꼭 변수에 할당해야 하는가?
반드시 변수에 할당해야 클로저가 동작하는 것은 아님.
중요한 것은 반환된 내부 함수가 외부 함수의 변수를 참조하고 있다는 점.
변수에 할당하는 이유는 일반적으로 반환된 내부 함수를 여러 번 호출하여 외부 함수의 상태를 유지하고 조작하기 위함.
함수 바깥에서 스코프에 접근한다는 게 뭔가?
일반적으로 함수 내부에서 선언된 변수는 함수 외부에서 접근할 수 없음.
하지만 클로저를 사용하면, 외부 함수가 반환한 내부 함수를 통해 마치 외부 함수의 스코프에 접근하는 것처럼 변수를 사용하고 변경할 수 있게 됨.
이는 직접적인 접근은 아니지만, 내부 함수라는 매개체를 통해 외부 스코프의 변수를 활용하는 것.
3. useState 훅의 클로저 활용:
JavaScript
function useState(initialValue) {
let state = initialValue;
return [
function getState() {
return state;
},
function setState(newValue) {
state = newValue;
},
];
}
const [count, setCount] = useState(0);
console.log(count()); // 0
setCount(1);
console.log(count()); // 1
useState 함수는 state 변수를 선언하고, getState 함수(현재 상태 반환)와 setState 함수(상태 업데이트)를 담은 배열을 반환getState와 setState 함수는 useState 함수의 스코프에 있는 state 변수를 클로저를 통해 접근하고 수정useState 함수가 종료된 후에도 count 함수(getState를 참조)와 setCount 함수(setState를 참조)는 여전히 state 변수에 접근할 수 있음function countWithoutClosure() {
let count = 0;
return count;
}
console.log(countWithoutClosure()) // 0
console.log(count)
// 레퍼런스 에러, 자바스크립트가 렉시컬 스코프의 규칙을 따르기 때문
// 즉 함수 안에 선언된 변수는 함수 안에서만 접근 가능
count를 전역변수로 만들기
클로저를 활용하는 방법
function countWithClosure() {
let count = 0;
return {
increase: function(){
count++;
return count
},
decrease: function(){
count--++;
return count
},
getCount: function(){
return count
},
}
}
const counter = createCounter();
console.log(count.getCount()):
counter.increase();
console.log(count.getCount()):
counter.decrease();
console.log(count.getCount()):
즉 클로저 함수를 호출한 결과는 function으로 저장돼있을것임
그리고 각 function은 함수 내부의 변수를 접근함
즉 함수 내부의 변수가 메모리상에 없으면 사용할 수 없는 함수임
- 그래서 클로저에 의해 return시 함수 실행에 필요한 상위 스코프도 함께 메모리로 가져오는 것
- 클로저에 의해 메모리에 캡쳐돼있기 때문
- counter에 다른 값이 할당되지 않는 이상태는 계속 유지됨
function useState(initialValue){
let state = initialValue;
return [
function getState(){
return state;
},
function setState(newValue) {
state = newValue;
}
]
}
const [count, setCount] = useState(0);
console.log(count()); // 0
setCount(1);
console.log(count()) // 1