데브코스 11일차 ( 24.10.28 월 ) JavaScript

워니·2024년 10월 31일
0

Programmers Front-end

목록 보기
11/27

[Section 01] JavaScript 기초


< 09. 클로저 >

1. 가비지 컬렉터

  • 메모리에서 더 이상 사용되지 않는 객체를 자동으로 감지하고,
    해당 메모리를 회수하여 메모리에서 삭제해주는 시스템
  • 모든 프로그래밍 언어에는 가비지 컬렉터가 내장되어 있다.
  • 자바스크립트는 자동으로 구축되어 있음
    (개발자가 메모리 회수를 걱정할 이유는 없음)

2. 가비지 컬렉션

  • 메모리가 회수 되는 과정
  • 자바스크립트에서는 실행 컨텍스트가 콜스택에서 제거될 때
    가비지 컬렉션을 자동으로 수행한다.
  • 컨텍스트의 레코드 값이 참조 중일 때는 자동으로 수행하지 않음

3. 메모리 누수

  • 쓸 데 없는 메모리가 쌓여 낭비되는 것

4. 클로저

  • 내부 함수에서 외부 변수를 참조하면서 반환하는 경우
  • 자유 변수를 참조하는 내부 함수는 반환하는 경우
  • 가비지 컬렉션의 대상이 되지 못하고 메모리가 제거 되지 못해 남아있는 현상
  • 클로저를 사용할 일은 거의 없지만!! 면접 단골 질문!!
클로저는 내부 함수에서 자유 변수를 가리키고 있는 함수를 의미합니다.
그래서 주로 캡슐화가 필요한 로직에 사용합니다.
하지만, 가비지 컬렉터에 회수되지 못하고 메모리가 낭비되는 현상이 발생합니다.
  • 클로저가 많으면 회수 되지 않는 메모리가 많아진다는 것
  • 자바스크립트의 가비지 콜렉터 시스템을 위협하는 행위!
  • null값을 입력하여 클로저 패턴을 끊어줘야 한다.
function outer() {
  let a = 10; // 자유 변수(free variable)
  return function inner() {
    // function inner()만 작성하면 함수를 반환하는 것이 아님
    // console.log(a)를 반환하게 됨
    a++;
    console.log(a);
  };
}

const fn = outer();
console.log(fn); // [Function: inner]
fn(); // 11

4.1. 클로저 사용 패턴

4.1.1. 캡슐화 (은닉화)

  • 예시
    let count = 0;
    function counter() {
      return {
        increment: function () {
          count++;
          return count;
        },
        decrement: function () {
          count--;
          return count;
        },
      };
    }
    let mycount = counter();
    
    console.log(mycount.increment()); // 1
    
    count = 20;
    console.log(mycount.increment()); // 21
    
    mycount = null;
    										// 캡슐화가 되지 않아 외부에서 조작이 가능함
    										// 변수 값을 함수 내부로 은닉화 필요
    
    function counter() {
      let count = 0; // count가 캡슐화(은닉화) 된 것
      return {
        increment: function () {
          count++;
          return count;
        },
        decrement: function () {
          count--;
          return count;
        },
      };
    }
    let mycount = counter();
    
    console.log(mycount.increment()); // 1
    
    count = 20; // 외부 조작 적용 안 됨
    console.log(mycount.increment()); // 2
    
    mycount = null;
    						// 안전한 변수를 만들 수 있다. 이런 경우 클로저를 사용함

4.1.2. 함수 팩토리

  • 특정 기능을 하는 함수를 고정할 때 사용
  • 예시
    function makeMultiple(multipler) {
    	// multipler가 클로저되는 것
      return function (x) {
        return x * multipler;
      };
    }
    let double = makeMultiple(2);
    let triple = makeMultiple(3);
    
    console.log(double(5)); // 5 * 2
    console.log(triple(5)); // 5 * 3
    											// 은닉화를 기본으로 깔면서 특정기능 함수를 고정
    
    double = null; // 메모리 해제, 자유변수 제거
    triple = null; // 메모리 해제, 자유변수 제거

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

  • 동기 언어 : 특정 코드가 실행되는 것을 기다려줌
  • 비동기적으로 코드를 실행할 수 있게 해줌
  • 예시
    function fetcData(url) {
      let result;
      return function (callback) {
        setTimeout(() => {
          result = "Fetched... success";
          callback(result);
        }, 1000); // 1초 뒤에 실행
      };
    }
    let fetchFromNaver = fetcData("https://www.naver.com");
    fetchFromNaver((data) => console.log(data));
    
    fetchFromNaver = null;

4.1.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 memoizationFn = memoization(slowFunction);
    console.log(memoizationFn(5)); // 7.211초
    console.log(memoizationFn(5)); // 여기부터는 빠르게 바로 출력됨
    console.log(memoizationFn(5));
    console.log(memoizationFn(5));
    
    memoizationFn = null; 
    // 기능을 다 쓰고 나면 항상 메모리 해제를 해줘야 함

연습 문제

문제 1: 카운터 만들기

설명: 클로저를 이용해 카운터 함수를 만드세요.
이 함수는 호출할 때마다 1씩 증가하는 값을 반환해야 합니다.

  • 기본 제공 코드:
    function createCounter() {
        // 여기에 코드를 작성하세요
    }
    
    const counter = createCounter();
    console.log(counter()); // 1
    console.log(counter()); // 2
    console.log(counter()); // 3
  • 문제 풀이
    function createCounter() {
      let a = 0;
      return function inner() {
        a++;
        return a;
      };
    }
    let counter = createCounter();
    console.log(counter()); // 1
    console.log(counter()); // 2
    console.log(counter()); // 3
    
    counter = null;
    
    // 강사님 풀이
    function createCounter() {
      let count = 0;
      return function () {
        count++;
        return count;
      };
    }
    let counter = createCounter();
    console.log(counter()); // 1
    console.log(counter()); // 2
    console.log(counter()); // 3
    
    counter = null;

문제 2: 비공식적인 캐싱

설명: 숫자를 제곱하는 함수를 만들고, 같은 숫자가 호출될 경우 결과를 캐싱하여 효율적으로 반환하세요.

  • 기본 제공 코드:
    function square() {
        // 여기에 코드를 작성하세요
    }
    
    console.log(square(4)); // 16
    console.log(square(4)); // 16 (캐시 사용)
    console.log(square(5)); // 25
  • 문제 풀이
    function square(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) {
      function inner() {
        num **= 2;
        return num;
      }
      return inner();
    }
    
    let squareFn = square(slowFunction);
    console.log(squareFn(4)); // 16
    console.log(squareFn(4)); // 16 (캐시 사용)
    console.log(squareFn(5)); // 25
    
    squareFn = null;
    
    // 강사님 풀이
    function squarec() {
      const cache = {};
      return function (num) {
        if (cache[num]) return cache[num];
        cache[num] = num * num;
        return cache[num];
      };
    }
    let square = squarec();
    
    console.log(square(4)); // 16
    console.log(square(4)); // 16 (캐시 사용)
    console.log(square(5)); // 25
    
    square = null;

문제 3: 지연 실행

설명: 주어진 시간 후에 실행되는 함수를 반환하는 클로저를 작성하세요.

  • 기본 제공 코드:
    function delayExecution(ms) {
        // 여기에 코드를 작성하세요
    }
    
    const delayedFunc = delayExecution(1000);
    delayedFunc(() => console.log("Executed after 1 second"));
  • 문제 풀이
    강사님 풀이
    function delayExecution(ms) {
      return function (callback) {
        setTimeout(callback, ms);
      };
    }
    
    let delayedFunc = delayExecution(1000);
    delayedFunc(() => console.log("Executed after 1 second"));
    
    delayedFunc = null;

문제 4: 고유 ID 생성기

설명: 고유한 ID를 생성하는 함수를 만드세요. 각 호출마다 증가하는 ID를 반환해야 합니다.

  • 기본 제공 코드:
    function createIdGenerator() {
        // 여기에 코드를 작성하세요
    }
    
    const getId = createIdGenerator();
    console.log(getId()); // 1
    console.log(getId()); // 2
    console.log(getId()); // 3
    
  • 문제 풀이
    강사님 풀이
    function createIdGenerator() {
      let id = 0;
      return function () {
        id++;
        return id;
      };
    }
    
    let getId = createIdGenerator();
    console.log(getId()); // 1
    console.log(getId()); // 2
    console.log(getId()); // 3
    
    getId = null;

문제 5: 메모이제이션

설명: 피보나치 수열을 계산하는 함수를 만들고, 결과를 메모이제이션하여 성능을 개선하세요.

  • 기본 제공 코드:
    function fibonacci() {
        // 여기에 코드를 작성하세요
    }
    
    console.log(fibonacci(10)); // 55
    console.log(fibonacci(10)); // 55 (메모이제이션 사용)
  • 문제 풀이
    function fibonacciC() {
      const cache = {};
      return function fib(n) {
        if (n <= 1) return n;
        if (cache[n]) return cache[n];
        cache[n] = fib(n - 1) + fib(n - 2);
        return cache[n];
      };
    }
    let fibonacci = fibonacciC();
    console.log(fibonacci(10)); // 55
    console.log(fibonacci(10)); // 55 (메모이제이션 사용)
    
    fibonacci = null;

문제 6: 문자열 결합

설명: 문자열을 결합하는 함수를 작성하고, 이전에 결합된 문자열을 기억하게 하세요.

  • 기본 제공 코드:
    function createStringCombiner() {
        // 여기에 코드를 작성하세요
    }
    
    const combiner = createStringCombiner();
    console.log(combiner("Hello")); // "Hello"
    console.log(combiner(" World")); // "Hello World"
  • 문제 풀이
    function createStringCombiner() {
      let result = "";
      return function (str) {
        result += str;
        return result;
      };
    }
    
    const combiner = createStringCombiner();
    console.log(combiner("Hello")); // "Hello"
    console.log(combiner(" World")); // "Hello World"

문제 7: 객체 속성 카운터

설명: 객체의 속성 개수를 세는 함수를 작성하세요. 이 함수는 객체를 클로저로 기억해야 합니다.

  • 기본 제공 코드:
    function createPropertyCounter() {
        // 여기에 코드를 작성하세요
    }
    
    const counter = createPropertyCounter();
    console.log(counter({ a: 1, b: 2 })); // 2
    console.log(counter({ a: 1 })); // 1
  • 문제 풀이
    function createPropertyCounter() {
      let count = 0;
      return function (obj) {
        for (let key in obj) {
          // key를 실제로 사용하지 않음, 배운게 for...in문이라서 사용한 것임
          // let _ in obj 로 표현해도 됨
          count++;
        }
        return count;
      };
    }
    
    const counter = createPropertyCounter();
    console.log(counter({ a: 1, b: 2 })); // 2
    console.log(counter({ a: 1 })); // 1

문제 8: 배열 필터링

설명: 주어진 배열에서 특정 조건을 만족하는 요소만 필터링하는 함수를 작성하세요.

  • 기본 제공 코드:
    function createFilter(condition) {
        // 여기에 코드를 작성하세요
    }
    
    const filterEven = createFilter(num => num % 2 === 0);
    console.log(filterEven([1, 2, 3, 4, 5])); // [2, 4]
  • 문제 풀이
    function createFilter() {
      const result = [];
      return function (array) {
        for (let num of array) {
          if (num % 2 === 0) result.push(num);
        }
        return result;
      };
    }
    
    const filterEven = createFilter();
    console.log(filterEven([1, 2, 3, 4, 5])); // [2, 4]
    
    // 강사님 풀이
    function createFilter(condition) {
      return function (arr) {
        const result = [];
        for (let i = 0; i < arr.length; i++) {
          if (condition(arr[i])) result.push(arr[i]);
        }
        return result;
      };
    }
    
    let filterEven = createFilter((num) => num % 2 === 0);
    console.log(filterEven([1, 2, 3, 4, 5])); // [2, 4]

문제 9: 다중 카운터

설명: 여러 개의 카운터를 동시에 관리할 수 있는 함수를 작성하세요. 각 카운터는 별개의 값을 가지고 있어야 하며, 특정 카운터를 지정하여 그 카운터만 증가시키는 기능이 필요합니다.

  • 기본 제공 코드:
    function createMultiCounter() {
        // 여기에 코드를 작성하세요
    }
    
    const counters = createMultiCounter();
    const counterA = counters('A');
    const counterB = counters('B');
    
    console.log(counterA()); // 1
    console.log(counterA()); // 2
    console.log(counterB()); // 1
    console.log(counterA()); // 3
    console.log(counterB()); // 2
    
  • 문제 풀이
    강사님 풀이
    
    function createMultiCounter() {
      const counts = {};
      return function (label) {
        if (!counts[label]) counts[label] = 0;
        return function () {
          counts[label]++;
          return counts[label];
        };
      };
    }
    
    const counters = createMultiCounter();
    const counterA = counters("A");
    const counterB = counters("B");
    
    console.log(counterA()); // 1
    console.log(counterA()); // 2
    console.log(counterB()); // 1
    console.log(counterA()); // 3
    console.log(counterB()); // 2

문제 10: 고차 함수로 커스터마이징된 계산기

설명: 기본 연산을 수행할 수 있는 계산기를 작성하세요. 이 계산기는 특정 연산을 클로저로 기억하여 그 연산만 수행할 수 있도록 합니다.

  • 기본 제공 코드:
    function createCalculator(operator) {
        // 여기에 코드를 작성하세요
    }
    
    const add = createCalculator((a, b) => a + b);
    console.log(add(2, 3)); // 5
    console.log(add(10, 5)); // 15
    
    const multiply = createCalculator((a, b) => a * b);
    console.log(multiply(2, 3)); // 6
    console.log(multiply(10, 5)); // 50
    
  • 문제 풀이
    강사님 풀이
    function createCalculator(operator) {
      return function (n1, n2) {
        return operator(n1, n2);
      };
    }
    
    const add = createCalculator((a, b) => a + b);
    console.log(add(2, 3)); // 5
    console.log(add(10, 5)); // 15
    
    const multiply = createCalculator((a, b) => a * b);
    console.log(multiply(2, 3)); // 6
    console.log(multiply(10, 5)); // 50

< 하루 정리 >

오늘도 수업을 듣는 내내 따라가기 바빠서 뇌가 폭발하는 듯 했다.
항상 어렵지만 계속 고민하고 복습하면서 결국엔 하나 하나씩 이해를 해 나갈 때 마다 힘든 것보다
뿌듯한 게 더 크기 때문에 열심히 하고 버틸 수 있는 것 같다.
지금까지 정리해왔던 내용들을 티스토리에 다시 옮기기도 해야 하고, 
문제도 다시 풀어봐야 해서 시간이 계속 부족하다.
주어진 시간은 한정적인데 할 게 너무 많은 느낌이라 조급한 마음이 자꾸 생긴다.

오늘은 함수 연습 문제 중 연습문제와 연습문제+를 다시 풀어봤다.
애너그램 문제에서 결국 막혀 버려서 답답해 하던 중 팀원들에게 도움을 청했고, 
수빈님께서 아주 자세하게 설명해주셨다.
손이 닿지 않는 부분이 엄청 간지러웠는데 시워~~언하게 긁어주신 느낌.
a || b 에서 a가 true면 a, a가 false면 b를 다시 확인한다는 기본적인 걸 잊어버릴 줄 
상상도 못했다.
나도 나름대로는 정말 세세하게 필기하고 메모한다고 생각했는데 수빈님이 코드 작성하시면서 
메모하신 걸 보고는 정말 나름대로에 불과했구나라는 걸 느꼈다. 이래서 팀플이 중요한 건가... 
다들 나보다 한참 어리지만 정말 실력들이 대단하고 멋지다. 
나도 언젠가는 누군가에게 피드백도 해줄 수 있는 사람이 되길..
profile
첫 시작!

0개의 댓글