타입스크립트에서의 클로저
이 글에서는 타입스크립트에서의 클로저에 대해 설명합니다.
YouTube Video
타입스크립트에서의 클로저
클로저란 무엇인가?
클로저란 함수가 정의된 스코프(환경)에 대한 참조를 저장할 수 있는 기능을 말하며, 정의된 스코프 외부에서 호출되더라도 그 환경에 접근할 수 있습니다. 아래에서는 타입 주석(type annotations)을 포함하여 클로저를 설명합니다.
간단히 말하자면, 클로저는 함수와 함수가 정의된 변수 환경을 결합하여, 함수 호출 시 그 환경에 접근할 수 있게 합니다.
클로저의 기본 작동 메커니즘
타입스크립트에서 함수가 다른 함수 내부에 정의될 경우, 내부 함수는 외부 함수의 변수에 접근할 수 있다는 점이 분명해집니다. 여기 타입 주석이 포함된 클로저의 기본 예제가 있습니다.
1function outerFunction(): () => void {
2 let outerVariable: string = "I am from outer function";
3
4 function innerFunction(): void {
5 // The inner function accesses the variable of the outer function
6 console.log(outerVariable);
7 }
8
9 return innerFunction;
10}
11
12const closure: () => void = outerFunction();
13closure(); // "I am from outer function"
outerFunction
의 반환 타입은() => void
로, 함수를 반환한다는 것을 나타냅니다.innerFunction
의 타입은 명시적으로void
로 설정되어 있으며, 반환값이 없음을 나타냅니다.
클로저의 용도와 이점
데이터 캡슐화
아래는 데이터를 캡슐화하기 위해 타입 주석이 포함된 클로저의 예제입니다.
1function createCounter(): () => number {
2 let count: number = 0;
3
4 return function (): number {
5 count += 1;
6 return count;
7 };
8}
9
10const counter: () => number = createCounter();
11console.log(counter()); // 1
12console.log(counter()); // 2
13console.log(counter()); // 3
createCounter
함수는() => number
타입의 함수를 반환합니다.count
변수는number
타입으로 정의되며, 클로저 내에서 조작됩니다.
고차 함수 (Higher-Order Functions)
클로저는 고차 함수를 생성할 때 유용합니다. 아래는 명확한 타입 주석이 포함된 고차 함수의 예제입니다.
1function createMultiplier(multiplier: number): (value: number) => number {
2 return function (value: number): number {
3 return value * multiplier;
4 };
5}
6
7const double: (value: number) => number = createMultiplier(2);
8console.log(double(5)); // 10
9
10const triple: (value: number) => number = createMultiplier(3);
11console.log(triple(5)); // 15
createMultiplier
는number
타입의 인수를 받고(value: number) => number
타입의 함수를 반환합니다.- 내부 함수도
number
타입의value
를 받아 결과를number
타입으로 반환합니다.
타입스크립트에서 클로저 구현 예제
타입 주석이 추가된 클로저로 범위 제한 카운터를 구현해 보세요.
1function rangeCounter(min: number, max: number): () => number | string {
2 let count: number = min;
3
4 return function (): number | string {
5 if (count <= max) {
6 return count++;
7 } else {
8 return `Count has exceeded the maximum value: ${max}`;
9 }
10 };
11}
12
13const counter: () => number | string = rangeCounter(1, 5);
14
15console.log(counter()); // 1
16console.log(counter()); // 2
17console.log(counter()); // 3
18console.log(counter()); // 4
19console.log(counter()); // 5
20console.log(counter()); // "Count has exceeded the maximum value: 5"
rangeCounter
함수는number
또는string
을 반환하는 함수를 반환합니다.- 내부 함수에서
count
가max
를 초과하면string
타입 메시지를 반환하고, 그렇지 않으면number
타입을 반환합니다.
클로저 사용 시 주의사항
클로저로 인한 잠재적 메모리 누수
클로저는 외부 범위의 변수를 유지할 수 있으며, 이는 때때로 메모리 누수를 유발할 수 있습니다. 불필요한 클로저는 명시적으로 메모리에서 해제해야 합니다.
1function createLeak(): () => void {
2 // Large array consuming significant memory
3 const largeArray: string[] = new Array(1000000).fill("leak");
4
5 // Closure capturing `largeArray`
6 return function (): void {
7 console.log(largeArray[0]); // Using the captured array
8 };
9}
10
11// Create a closure that holds a reference to the large array
12let leakyFunction = createLeak();
13
14// The large array is not released as `leakyFunction` still references it
15
16// When the object is no longer needed
17leakyFunction = null; // Explicitly remove the reference
- 이 코드에서
createLeak
내에서 생성된largeArray
는 스코프를 벗어나면 해제되어야 하지만, 클로저가largeArray
를 캡처하기 때문에 해제되지 않습니다.leakyFunction
이 존재하는 한, 이 불필요한 메모리는 계속 유지됩니다. - 객체나 변수가 더 이상 필요하지 않을 경우 참조를
null
로 설정하면 가비지 컬렉터가 이를 감지하고 메모리를 해제할 수 있습니다.
루프에서 클로저의 오용
루프 내에서 클로저를 생성할 때 동일한 변수를 참조하는 문제의 가능성이 있습니다. 다음 예제는 변수가 i
가 올바르게 작동하지 않는 경우를 보여줍니다.
1for (var i: number = 0; i < 3; i++) {
2 setTimeout((): void => {
3 console.log(i);
4 }, 1000);
5}
6// Output: 3, 3, 3
이 코드는 루프가 끝날 때 i
가 값 3을 참조하므로 원하는 결과를 도출하지 않습니다. 이를 해결하려면 let
을 사용하여 스코프를 분리하거나 즉시 호출 함수(IIFE)를 사용하세요.
1for (let i: number = 0; i < 3; i++) {
2 setTimeout((): void => {
3 console.log(i);
4 }, 1000);
5}
6// Output: 0, 1, 2
let
을 사용하면 각 루프 반복마다 i
의 스코프가 분리되어 기대한 결과를 얻을 수 있습니다.
요약
TypeScript에서는 클로저를 사용하여 타입 시스템을 활용함으로써 더 안전하고 예측 가능한 코드를 작성할 수 있습니다. 클로저를 올바르게 사용하면 데이터 캡슐화 및 고차 함수의 유연한 설계를 할 수 있습니다. 추가적으로, 클로저를 사용할 때 메모리 관리 및 의도치 않은 스코프 참조에 주의해야 합니다.
위의 기사를 보면서 Visual Studio Code를 사용해 우리 유튜브 채널에서 함께 따라할 수 있습니다. 유튜브 채널도 확인해 주세요.