[TIL / DAY 11] 클로저

miseullang·2024년 10월 28일

✅ 강의 내용 정리


📍 가비지 콜렉터 및 가비지 콜렉션


가비지 콜렉터

메모리에서 더이상 사용되지 않는 객체를 자동으로 감지하고, 해당 메모리를 회수하여 메모리에서 삭제해주는 시스템을 말한다.

가비지 콜렉션

메모리가 회수되는 과정을 가비지 콜렉션이라고 한다.

메모리 누수

쓸데 없는 메모리가 쌓이는 것, 메모리가 할당만 되고 해제되지 않아 사용하지 않는 메모리가 누적되는 것

📍 클로저


클로저에 대해 설명하기

클로저는 함수가 선언된 환경의 렉시컬 스코프를 기억하여 자신이 선언된 범위 밖에서 실행될 때도 해당 스코프에 접근할 수 있>는 함수이다.

즉, 내부 함수에서 자유 변수를 가리키고 있는 함수를 의미하며, 주로 캡슐화가 필요한 로직에 사용한다.

정의

기본적으로 함수가 종료되면 그 함수 내부에서 사용된 지역 변수와 메모리는 더 이상 참조되지 않으면 가비지 컬렉터에 의해 회수된다. 그래서 보통은 외부에서 해당 함수의 내부 변수나 상태에 접근할 수 없게 된다.

하지만 외부 함수 내에서 선언된 내부 함수가 해당 외부 함수의 변수를 참조하는 경우, 그 변수는 외부 함수가 종료된 이후에도 메모리에서 유지된다. 이러한 내부 함수는 외부 함수의 스코프에 있는 변수에 접근할 수 있고, 그 값을 기억하며 사용할 수 있다.

즉, 클로저란 함수가 자신이 선언된 환경(스코프)의 변수에 접근할 수 있는 개념이다. 외부 함수가 종료된 이후에도 내부 함수가 그 변수들을 기억하며 활용할 수 있는 현상을 의미한다.

가비지 컬렉션의 대상이 되지 못하고 메모리 제거가 되지 못해서 메모리에 남아있는 현상클로저라고 한다.

내부 함수에서 외부 변수를 참조하면서 반환되는 경우, 자유 변수를 참조하는 내부 함수를 반환하는 것을 클로저라고 하기도 한다.

function createCounter() {
  let count = 0; // 외부 함수의 지역 변수 (자유 변수 / 특정 스코프나 함수의 관리를 받고 있지 않다)

  return function() {
    count++; // 내부 함수가 외부 함수의 변수에 접근
    console.log(`Current count: ${count}`);
  };
}

const counter = createCounter(); // 외부 함수 호출로 클로저 생성

counter(); // 출력: Current count: 1
counter(); // 출력: Current count: 2
counter(); // 출력: Current count: 3

console.log(count); // 종료된 함수를 반환할 수 없는 예 => ReferenceError: count is not defined

내가 짠 코드에 클로저가 많으면

⇒ 회수되지 않는 메모리가 많아진다는 것이므로, 자바스크립트의 가비지 콜렉터 시스템을 잘 사용하지 못하는 것이며, 효율적으로 메모리를 사용하기도 어려워진다.

따라서 클로저 패턴은 남용 금지!

🌟 클로저 사용패턴

1. 캡슐화(또는 은닉화) 클로저 패턴 중 가장 많이 구현해야 할 패턴

안전한 변수를 만드는 목적으로 클로저를 사용한다.

은닉화를 하는 이유
예제를 구현하는 것은 꼭 클로저가 아니어도 가능하지만, 클로저를 사용하지 않고 구현하려면, 전역 변수객체를 사용한다. 하지만 이런 경우 변수가 전역 스코프에 노출되어 누구나 count 값을 직접 수정할 수 있다.
이런 위험을 방지하고자 은닉화를 하는 것!

function counter() {
   let count = 0;
   return {
       increment: function () {
           count++;
           return count;
       },
       decrement: function () {
           count--;
           return count;
       }
   };
}

const mycount = counter();

console.log(mycount.increment()); // 1
console.log(mycount.increment()); // 2
console.log(mycount.increment()); // 3

2. 함수 팩토리

특정 기능을 하는 함수를 고정할 때 사용

은닉화를 기본으로 하면서, 특정한 기능을 수행할 수 있도록 할 때

function makeMultiple(multiplier) {
   return function (x) {
       return x * multiplier;
   };
}

let double = makeMultiple(2);
let triple = makeMultiple(3);

console.log(double(5)); // 10
console.log(triple(5)); // 15

// ---- 자유변수 제거 ----
double = null;
triple = null;

3. 비동기 프로그래밍 패턴

자바스크립트는 기본적으로 동기 언어라서 특정 코드가 실행되는 것을 기다린다.

하지만 데이터 요청, 파일 읽기 등 시간이 오래 걸리는 작업의 경우 비동기적으로 처리하는 것이 효율적이다. 이때 클로저는 비동기 작업의 상태와 콜백을 관리하는데 유용하게 사용된다.

질문 : 클로저가 어떤 점에서 비동기 작업의 상태와 콜백을 관리하는 데 유용한가요?

파일 읽기를 예로 들면, 파일을 읽기 위해서 readFile() 함수를 만들었고,
이 함수 안에서 파일 읽기를 처리할 경우 20초가 걸린다고 가정해볼게요.
함수를 만들었다는 거 자체가, 일단 readFile 컨텍스트에서 코드를 실행하겠다는건데
코드를 기다리자니 20초동안 기다려야 되고, 무시하자니 readFile 컨텍스트가 콜스택에서 제거 되면 메모리에서 제거가 되니까 접근을 할 수 없잖아요?
이럴때 readFile 결과를 저장할 변수를 클로저 형식으로 구현하면, readFile 컨텍스트가 삭제되어도 참조가 유지되기 때문에 읽기 작업이 완료가 되면, 저장한 클로저를 활용해서 결과 값에 접근할 수 있으니까 유용하지요~! w.수코딩님

클로저가 어떤 부분에서 비동기 프로그래밍에 유리하다는 건지 잘 안 와닿았는데 강사님이 바로 해결해주심...! 외쳐 갓갓

function fetcData(url) {
  let result;
  return function (callback) {
    setTimeout(() => {
      result = "Fetched... Success";
      callback(result);
    }, 1000);
  };
}

let fetchFromNaver = fetcData("https://www.naver.com");
fetchFromNaver((data) => console.log(data));

// --- 자유변수 제거 ---
fetchFromNaver = null;

4. 메모이제이션

시간 복잡도가 높은 함수를 짰을 때, 처음 요청은 오래 걸리더라도, 재실행 시에는 빠르게 결과를 리턴할 수 있다.

function memoization(fn) {
  const cache = {};
  return function (...args) {
    const key = JSON.stringify(args); // JSON 변환 -> 문자열로 취급됨
    if (cache[key]) return cache[key];
    const result = fn(...args);
    cache[key] = result;
    return result;
  };
}

function slowFunction(num) {
  for (let i = 0; i < 9999999999; i++);
  return num * 2;
}

let memoizedFn = memoization(slowFunction);
console.log(memoizedFn(5));
console.log(memoizedFn(5));
console.log(memoizedFn(5));
console.log(memoizedFn(5));

--- 메모리 해제 ---
memoizedFn = null;

cache 객체에 사용된 값이 누적되면 그 부분도 메모리 누수가 되지 않게 메모리 해제를 해줘야 함.

⇒ 초기화 해줘야 하는 값은 let 키워드로 선언해야 함

** 클로저 패턴을 사용하고 나서는 메모리가 누수되지 않도록 해제해주는 작업이 필요하다.

클로저 함수는 항상 자유함수를 참조하는 내부 함수를 반환해야 한다.



💭 회고


이제 클로저가 뭐다, 하는 것까진 알겠는데... 구현 하라고 하면 못 하겠음 ߹ - ߹

profile
괴발개발 💻

0개의 댓글