함수가 선언될 당시의 렉시컬 환경(Lexical Environment) 을 함수와 함께 묶어 기억하는 조합을 클로저 라고 한다.
이를 자세히 설명하기 위해서는 동작 원리를 알아야 한다.
먼저 다음과 같은 형태의 함수를 선언한다.
function 외부함수() {
let 내부변수 = '변수 내용';
function 내부함수() {
console.log(내부변수);
}
return 내부함수;
}
const 나만의함수 = 외부함수();
나만의함수() //'변수 내용'
함수는 실행되는 순간 실행 컨텍스트(Execution Context) 에 자신의 렉시컬 환경, 즉 함수 주변 정보와 내부 정보가 기록되고 함수의 실행이 끝나면 가비지 컬렉션이 메모리를 정리해 기록된 렉시컬 환경의 정보가 날아가게 된다.
하지만 위의 함수처럼 중첩된 함수가 존재하고 내부함수
가 외부함수
의 내부변수
를 참조하고 있다면 가비지 컬렉션은 이 함수의 메모리 정리를 스킵하게 된다.
따라서 클로저는 완료되지 않은 실행 컨텍스트 내부에 기록된 렉시컬 환경을 계속해서 참조할 수 있게 된다. 이는 함수가 선언된 시점의 스코프를 기억해 스코프에 있는 변수에 접근할 수 있도록 한다.
특징을 설명하기 위해 다음과 같은 클로저를 선언한다.
function createCounter() {
let count = 0;
return function() {
count++;
return count;
}
}
const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
위의 함수를 보면 count
라는 변수의 값은 변하지만 외부에서 count
변수에 직접 값을 할당한 적은 없다.
이는 렉시컬 스코프(Lexical Scope)로 인해 외부에서는 내부의 변수에 접근할 수 없기 때문이다. 이를 이용해 클로저는 데이터 은닉에 유용하게 쓰일 수 있다.
다음은 은행 계좌와 관련된 클로저이다.
function BankAccount(initialBalance) {
let balance = initialBalance;
return {
deposit: function (amount) {
if (amount > 0) {
balance += amount;
console.log(`${amount}원 입금 완료. 잔고는 ${balance}원 입니다.`);
} else {
console.log('입금 금액은 0원보다 많아야 합니다.');
}
},
withdraw: function (amount) {
if (amount > 0 && amount <= balance) {
balance -= amount;
console.log(`${amount}원 출금 완료. 잔고는 ${balance}원 입니다.`);
} else {
console.log('출금할 수 없는 금액입니다.');
}
},
getBalance: function () {
console.log(`현재 잔고는 ${balance}원 입니다.`);
return balance;
},
};
}
const myAccount = BankAccount(1000);
myAccount.deposit(500); // 500원 입금 완료. 잔고는 1500원 입니다.
myAccount.withdraw(200); // 200원 출금 완료. 잔고는 1300원 입니다.
myAccount.getBalance(); // 현재 잔고는 1300원 입니다.
내 계좌 잔고를 나타내는 balance
라는 변수에 접근할 수 있는 방법은 내부에서 선언된 메서드인 deposite
, withdraw
, getBalance
를 이용해 간접적으로 balance를 조작하는 방법뿐이다.
이처럼 데이터를 외부로부터 숨기고, 내부 상태의 접근 방법을 제한하여상태를 보호하는 것을 객체의 무결성을 유지한다고 하며 이런 형태의 구현을 캡슐화(Encapsulation) 라고 한다.
클로저는 렉시컬 환경을 기억하고 계속해서 참조하여 상태를 유지할 수 있는데 이를 활용해 만들어진 것이 React의 useState hook 이다.
참조
클로저와 실행 컨텍스트의 깊은 이해
useState 동작 원리와 클로저
useState와 클로저의 관계