Javascript & Typescript/이해하기
[JS] 클로저(Closure) 이해하기
adjh54
2022. 10. 3. 14:20
반응형
해당 글의 목적은 클로져에 대한 정의를 이해하고 왜 사용을 해야 하는지에 대하여 이해를 돕기 위해서 작성한 글입니다.
💡 [참고] 클로저를 이해하기 위해서는 이전에 작성한 '스코프'에 관련된 글을 읽고 오시면 이해하는데 도움이 됩니다.
1) 클로저(Closure)
💡 클로저(Closure)
- 함수와 그 함수가 선언된 어휘적 환경(Lexical Environment)의 조합을 의미합니다.
- 즉, 함수가 그 당시에 선언 되었을때의 모든 정보(변수, 환경 내의 정보들)을 기억하고, 호출되어 처리가 끝나고 소멸 된 이후에도 그 당시의 모든 정보를 기억하며 접근할 수 있음을 의미합니다.
- 외부/내부 함수와의 관계에서는 외부 함수가 호출되어 실행을 마치고 소멸이 된 이후에도 내부 함수는 외부 함수의 그 당시 선언되었을 때의 환경을 기억하고 있어 이에 접근을 할 수 있음을 의미합니다.
💡 예제 설명
- 해당 구조에서는 makeCounter라는 외부 함수와 incrementCounter라는 내부 함수로 구성이 되어 있고, 외부 함수를 콘솔로 출력을 하고 있는 예시입니다,
1. 외부 함수(makeCounter)
- 외부 함수에서는 count라는 변수가 0으로 값을 할당하고 있고, 내부 함수를 리턴해주는 구조로 되어 있습니다.
2. 내부 함수(incrementCounter)
- 내부 함수에서는 외부 함수의 변수인 count 값을 1씩 증가시키고 그 값을 리턴해주고 있는 구조로 되어 있습니다.
3. 외부 함수 호출
- counter라는 변수에서는 외부함수(makeCounter)의 값을 할당하고 있습니다.
- console.log()에서는 외부 함수의 리턴 값을 콘솔로 반환하고 있습니다. 순차적으로 값이 1 - 2 - 3이라는 값을 출력하고 있습니다.
- 해당 예시를 보았을 때, 외부 함수를 호출하는 경우 반환되는 count의 값이 최초 할당된 0의 값에 1이 증가된 값으로 1 - 1 - 1이라는 값이 예측이 되지만, 반환되는 값을 1 - 2 - 3이라는 값이 반환되고 있습니다.
- 즉, makeCounter 함수는 호출하고 소멸이 되더라도 'count라는 변수 값을 기억'하고 있기에 1 - 1 - 1이라는 값이 아닌 1 - 2 - 3이라는 값이 반환되고 있습니다. 이는 함수가 호출되어 소멸되더라도 외부 함수의 변수를 기억하여 접근이 가능함을 확인하였습니다.
// 외부 함수
const makeCounter = () => {
let count = 0;
// 내부 함수
const incrementCounter = () => {
count++;
return count;
};
return incrementCounter;
};
const counter = makeCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3
💡 [참고] 클로져 정의 - MDN
2) 클로저는 왜 사용해야 하는 것일까?
1. 데이터의 은닉 및 접근 제한(캡슐화) : Private Method 형태로 구성
💡데이터의 은닉 및 접근 제한(캡슐화) : Private Method 형태로 구성 가능
- 자바스크립트 내에서는 다른 언어와 달리 메서드를 Private Method 형태로 선언할 수 있는 기능을 제공하지 않습니다.
- 그렇기에 이와 비슷한 형태로 데이터의 접근을 제한하는 캡슐화를 클로저 개념을 이용하여 구성할 수 있습니다.
[ 더 알아보기 ]
💡 Private Method
- 같은 클래스 내부의 특정 메서드에서만 해당 메서드를 호출이 가능한 것을 의미합니다. 이를 사용하면 코드의 제한적인 접근만을 제공한다는 장점이 있습니다.
- 전역 네임 스페이스를 관리하여 불 필요한 메서드가 공용 인터페이스를 혼란스럽게 만들지 않도록 합니다.
💡 사용 예시
- 해당 하단의 함수에서 counter라는 외부 함수 내에 increment(), decrement(), value() 함수라는 내부 함수로 캡슐화(은닉화)를 하였습니다.
- 이를 통해서 직접적으로 값을 바꾸는 increment(), decrement() 함수를 호출할 수 없는 정보 접근에 제한을 두었습니다.
import { useEffect } from 'react';
/**
* 클로저를 이용한 Private Method 구현하기
* @returns
*/
const closure1PrivateMethod = () => {
useEffect(() => {
console.log(counter.value()); // logs 0
counter.increment();
counter.increment();
console.log(counter.value()); // logs 2
counter.decrement();
console.log(counter.value()); // logs 1
}, []);
/**
* 숫자를 늘리고 줄이는 함수
*/
const counter = (() => {
// 외부의 함수의 변수를 선언합니다.
let count = 0;
// 전달 받은 값을
const changeBy = (value: number) => {
count += value;
};
return {
// 값을 증가합니다.
increment: (): void => {
return changeBy(1);
},
// 값을 줄입니다.
decrement: () => {
return changeBy(-1);
},
// 값을 출력합니다.
value: () => {
console.log(count);
return count;
},
};
})();
return <div></div>;
};
export default closure1PrivateMethod;
2. 상태 유지
💡 상태 유지
- 외부 함수의 실행이 끝나더라도, 외부 함수 내 변수를 사용할 수 있는 '상태유지' 특성을 이용하여 특정 데이터를 스코프 안에 두고 계속 최신 상태로 유지하여 사용합니다.
💡 사용 예시 : 클로저
- 해당 예시에서는 클로저를 이용하여, 외부 함수의 변수(count) 값을 기억하여서 호출할 때마다 출력될 때마다 그때의 환경을 기억하여 값이 1 - 2 -3으로 출력이 됩니다.
function Counter() {
let count = 0;
function increment() {
count += 1;
console.log(count);
}
return increment;
}
const counter = Counter();
counter(); // 1
counter(); // 2
counter(); // 3
💡 상태 유지 : useState()
- 해당 예시에서는 useState를 이용하여서 해당 컴포넌트 내에서 이를 유지하고 관리할 수 있는 형태를 의미합니다.
- 즉, increment() 함수를 호출하는 경우 State 내의 값이 1씩 증가한 값으로 컴포넌트 내에서 값이 유지됩니다.
import React, { useState } from 'react';
const Counter = () => {
const [count, setCount] = useState(0);
const increment = () => {
setCount(count + 1);
};
return (
<div>
<p>{count}</p>
<button onClick={increment}>Increment</button>
</div>
);
};
export default Counter;
💡 그럼 결국 클로저와 useState는 값의 상태를 유지한다는 점이 동일한 것 아닌가?
- 두 방법 모두 상태를 유지하는 기능을 하지만, useState는 React의 상태 관리 메커니즘을 활용하여 컴포넌트가 다시 렌더링 될 때 상태를 유지하고 업데이트하는 데 최적화되어 있습니다.
3. 전역 변수의 사용 억제 - 모듈화에 유리하다.
💡 전역 변수의 사용 억제 - 모듈화에 유리하다.
- 클로저 함수를 각각의 변수에 할당하면 각자 독립적으로 값을 사용하고 보존할 수 있습니다.
- 이와 같이 함수의 재사용성을 극대화 함수 하나를 독립적인 부품의 형태로 분리하는 것을 모듈화라고 합니다.
💡 사용예시
- 아래의 closureModules라는 컴포넌트가 존재합니다.
- 해당 컴포넌트에서는 counter라는 함수가 존재하며, 외부 함수에서는 count라는 변수를 0의 값으로 할당을 하였습니다.
- 또한 함수내에는 내부함수로 increment, decrement, value라는 함수가 존재합니다.
- 화면이 렌더링 된 이후에 useEffect()를 통해서 conter라는 외부함수에 접근하여 내부 함수인 increment와 decrement를 호출하고 있습니다. 해당 부분에서는 외부 함수에서 관리되는 count라는 값을 기억하여 해당 값이 관리가 됩니다.
- 그렇기에 클로저를 이용하여 데이터와 메서드를 묶어서 다닐 수 있기에 모듈화에 유리합니다.
import { useEffect } from 'react';
const closureModlues = () => {
useEffect(() => {
counter.increment();
counter.increment();
counter.increment();
console.log(counter.value()); // 3
counter.decrement();
counter.decrement();
counter.decrement();
console.log(counter.value()); // 0
}, []);
/**
* 숫자를 세는 클로저를 이용한 모듈
* @returns
*/
const counter = (() => {
let count = 0;
return {
// 값을 증가합니다.
increment: () => {
return (count += 1);
},
// 값을 줄입니다.
decrement: () => {
return (count -= 1);
},
// 값을 출력합니다.
value: () => {
return count;
},
};
})();
return (
<></>
);
};
export default closure3Modlues;
오늘도 감사합니다😀
반응형