[22.12.25 함수의 렉시컬 범위와 Closure 개념 보충 수정]
클로저(Closure)
클로저(Closure)란 함수가 선언될 때 해당 함수가 유효하게 동작할 수 있는 범위(렉시컬 범위)를 기억하고 있다가, 함수가 외부에서 호출될 때 그 유효 범위의 특정 변수를 참조할 수 있는 개념을 말합니다.
const createCount = () => {
let a = 0;
return () => {
return (a += 1);
};
};
const count = createCount();
console.log(count()); // 1
console.log(count()); // 2
console.log(count()); // 3
간단한 예시와 함께 살펴보면 함수 createCount 내부에는 변수 a가 선언되어 있고 해당 함수는 또 다른 함수를 반환하고 반환되는 함수는 변수 a + 1 한 값을 반환하고 있습니다.
상수 count에는 함수 createCount를 호출하였기 때문에 함수 createCount가 반환하는 함수를 할당받게 되며, 상수 count에는 함수가 할당된 것이 되기 때문에 count는 함수가 됩니다.
함수 count는 내부에서 변수 a를 사용하고 있는데, 변수 a는 함수 count 밖에서 선언된 변수입니다. 초기 0으로 선언되었기 때문에 1을 더하면 결과는 1이 되는 것이 당연한 결과이지만 이후의 함수 count 호출에서는 2와 3을 반환하는 것을 확인할 수 있습니다.
이때 함수 count를 클로저라고 할 수 있으며, 변수 a가 유효하게 동작할 수 있는 범위는 변수로 선언된 블록 범위(렉시컬 범위)를 기억하고 있다가 외부에서 함수 count가 호출될 때 유효한 범위의 특정변수(변수 a)를 참조하여 사용할 수 있게 됩니다. 변수 a의 값은 매번 0으로 시작하는 것이 아니고 누적이 됩니다.
*즉 클로저를 만들게 되면 전역에서 변수를 선언하여 사용하는 것과 같이 사용할 수 있습니다.
*변수 a를 전역적으로 선언하고 사용하게 되면 변수 a와 함수 createCount를 별도로 관리해주어야 하기 때문에 외부 함수 createCount에 선언해주고 클로저 하수(내부 함수 count)에서 변수 a를 누적하며 사용합니다.
const createCount = () => {
let a = 0;
return () => {
return (a += 1);
};
};
const count1 = createCount();
const count2 = createCount();
console.log(count1()); // 1
console.log(count1()); // 2
console.log(count1()); // 3
console.log(count2()); // 1
기존의 클로저 함수 count를 count1로 변경해주고 추가로 클로저함수 count2를 호출해주게 되면 기존의 값에 누적되는 것이 아닌 새롭게 누적을 시작하는 것을 확인할 수 있습니다.
이렇게 클로저 함수를 통해 외부함수(createCount)를 관리하게 된다면 렉시컬 범위에 선언된 변수 활용과 재사용이 가능해지게 됩니다.
*JavaScript의 모든 함수는 클로저에 해당합니다. 변수의 스코프라는 개념과 밀접합니다.
const global = 21
function createTaxCalculator(tax) {
function calculateTax(amount) {
return amount * tax;
}
return calculateTax;
}
함수의 매개 변수는 해당 함수 내에서만 사용할 수 있고 외부에서는 사용이 불가능합니다. 한편 함수 외부에서 선언된 전역 변수의 경우 함수 내부에서 사용이 가능합니다.
위의 코드의 경우 함수 createTaxCalculator(외부 함수)가 함수 calculateTax(내부 함수)를 감싸고 있습니다. 이때 내부 함수 calculateTax는 외부 함수 createTaxCalculator 변수와 매개변수를 사용이 가능합니다. 하지만 외부 함수의 경우 내부 함수에 선언된 변수나 매개 변수의 사용이 불가능합니다.
이는 함수가 선언될 때 새로운 렉시컬 범위를 만들어 범위 내에서 접근할 수 있는 변수를 등록합니다. 위의 코드로 예를 들자면 외부 함수는 tax라는 매개변수를 렉시컬 범위의 변수로 등록합니다. 전역 범위는 상수 global이 있습니다. 내부 함수의 경우도 새로운 렉시컬 범위를 만들어 해당 범위에 매개 변수 amount를 등록합니다.
만약 외부 함수의 매개 변수 tax에 새로운 값을 인수로 전달한다면 내부 함수는 새로운 값을 전달받은 tax를 사용하게 될 것입니다.
const vatAmount = createTaxCalculator(0.19);
반대로 매개 변수 tax의 값이 위처럼 고정이하면 내부함수는 tax의 값을 기억했다가 계속 사용하게 되고, 고정된 세율로 금액에 곱하여 값은 반환하게 됩니다.
let userName = "rati";
function greetUser() {
console.log(`Hi! ${userName}`);
}
greetUser(); // Hi! rati 출력
함수 greetUser는 외부 렉시컬 범위에 있는 변수를 참조하고 있습니다.
let userName = "rati";
function greetUser() {
console.log(`Hi! ${userName}`);
}
userName = "rati Lee";
greetUser(); //Hi! rati Lee 출력
이때 함수 greetUser 호출부 전에 변수 userName에 새로운 값을 재할당해주게 되면 당연하게 출력되는 결과도 위와 같이 변하게 됩니다.
변수 userName의 경우 함수 greetUser의 외부에 선언되어 있습니다. userName에 초기에 할당한 rati를 함수는 반영하게 됩니다. 이때 userName의 값이 변경되게 되면 greetUser는 변경된 값을 반영하게 됩니다.
때문에 함수를 생성하고 접근할 때 변수의 값을 복사하지 않고 변수 자체에 접근한다고 할 수 있습니다. 그래서 함수를 호출하면 해당 변수에서 찾을 수 있는 가장 최신의 값을 얻게 됩니다.
let userName = "rati";
function greetUser() {
let name = userName;
console.log(`Hi! ${name}`);
}
userName = "rati Lee";
greetUser(); // Hi! rati Lee 출력
변수 userName를 함수 내부에서 변수 name에 할당한 뒤 함수 greetUser를 실행하게 해 주었습니다. 이제 name는 함수의 내부 렉시컬에 속하게 되었습니다. 하지만 여전히 외부 렉시컬에 속하는 userName을 참조하고 있습니다. 이유는 위와 동일합니다.
function greetUser() {
let name = "merry";
console.log(`Hi! ${name}`);
}
let name = "rati";
greetUser(); // Hi! merry 출력
이번에는 함수 greetUser 내부 렉시컬에 변수 name을 만들어주었으며, 함수 외부 렉시컬에 동일한 이름을 가진 변수에 다른 값을 할당해 주었습니다. 결과는 함수 내부 렉시컬에 등록된 변수의 값을 참조한 콘솔이 출력이 됩니다.
이는 함수 실행 될 때 우선적으로 내부 렉시컬을 확인하여 변수를 찾고, 찾을 수 없을 때만 다음 단계로 넘어가 외부 렉시컬을 확인하게 됩니다. 때문에 함수 greetUser는 내부 렉시컬에서 변수 name을 찾았기 때문에 외부 렉시컬에서 찾을 필요가 없게 된 것입니다.
*JavaScript에서는 전역에서 선언된 변수를 더 이상 사용하지 않아도 해당 변수를 함수가 접근하고 있다면 제거하지 않습니다.
(앞서 언급한 대로 함수는 선언될 때 변수에 값에 접근하는 것이 아니라 변수 자체에 접근하여 변수의 값이 변할 경우 변한 값을 사용하기 때문입니다.)
'JavaScript' 카테고리의 다른 글
HTTP (0) | 2022.12.25 |
---|---|
함수 (2) | 2022.12.25 |
클래스 (0) | 2022.12.24 |
정규표현식 (0) | 2022.12.24 |
JavaScript 동작 원리(동기와 비동기) (0) | 2022.12.23 |