JavaScript Closure


자바스크립트에서 클로저(Closure)는 함수와 그 함수가 선언된 렉시컬 환경의 조합을 말합니다. 클로저를 통해 함수는 자신이 선언된 환경을 기억하고, 그 환경에 접근할 수 있습니다. 클로저는 자바스크립트의 강력한 기능 중 하나로, 함수형 프로그래밍 패턴에서 자주 사용되며, 데이터 은닉과 같은 다양한 응용이 가능합니다.

1. 클로저의 기본 개념

클로저는 함수가 자신이 선언된 환경을 기억하여, 함수가 호출되는 시점이 아닌 선언되는 시점의 환경에 접근할 수 있게 합니다.

function outerFunction() {
  const outerVariable = 'I am outside!';

  function innerFunction() {
    console.log(outerVariable);
  }

  return innerFunction;
}

const closureFunction = outerFunction();
closureFunction(); // I am outside!

위 예제에서 innerFunctionouterVariable을 참조합니다. outerFunction이 호출된 후에도 innerFunctionouterVariable에 접근할 수 있습니다. 이는 innerFunctionouterVariable을 포함한 환경을 기억하고 있기 때문입니다.

2. 클로저의 동작 원리

클로저는 다음과 같은 원리로 동작합니다.

  1. 함수 생성: 함수가 생성될 때, 함수의 스코프 체인이 생성됩니다.
  2. 렉시컬 환경: 함수가 생성된 위치의 렉시컬 환경이 함수에 저장됩니다.
  3. 변수 접근: 함수가 호출될 때, 저장된 렉시컬 환경을 통해 변수에 접근할 수 있습니다.

3. 클로저의 응용

클로저는 다양한 용도로 사용될 수 있습니다.

3.1 데이터 은닉

클로저를 사용하여 데이터 은닉을 구현할 수 있습니다. 이는 객체 지향 프로그래밍에서의 캡슐화를 구현하는 방식과 유사합니다.

function createCounter() {
  let count = 0;

  return {
    increment() {
      count++;
      console.log(count);
    },
    decrement() {
      count--;
      console.log(count);
    },
    getCount() {
      return count;
    }
  };
}

const counter = createCounter();
counter.increment(); // 1
counter.increment(); // 2
counter.decrement(); // 1
console.log(counter.getCount()); // 1

위 예제에서 count 변수는 createCounter 함수 내부에서만 접근할 수 있으며, 외부에서는 접근할 수 없습니다. 이는 createCounter 함수가 반환하는 객체의 메서드를 통해서만 count 변수에 접근할 수 있게 합니다.

3.2 함수 팩토리

클로저를 사용하여 함수 팩토리를 만들 수 있습니다. 이는 특정 설정이나 상태를 유지하는 함수들을 생성할 때 유용합니다.

function createGreeter(greeting) {
  return function(name) {
    console.log(`${greeting}, ${name}!`);
  };
}

const sayHello = createGreeter('Hello');
const sayHi = createGreeter('Hi');

sayHello('Alice'); // Hello, Alice!
sayHello('Bob'); // Hello, Bob!
sayHi('Charlie'); // Hi, Charlie!

위 예제에서 createGreeter 함수는 greeting 변수를 기억하는 클로저를 반환합니다. 각각의 클로저는 서로 다른 greeting 값을 유지합니다.

3.3 반복문과 클로저

클로저를 사용하여 반복문 내부의 비동기 처리를 올바르게 수행할 수 있습니다.

for (var i = 0; i < 3; i++) {
  setTimeout(function() {
    console.log(i);
  }, 1000);
}

// 3, 3, 3 (1초 후에 각각 출력)

위 예제에서는 var로 선언된 i 변수가 반복문이 끝난 후의 값을 참조합니다. 이를 해결하기 위해 클로저를 사용할 수 있습니다.

for (let i = 0; i < 3; i++) {
  setTimeout(function() {
    console.log(i);
  }, 1000);
}

// 0, 1, 2 (1초 후에 각각 출력)

또는 IIFE(Immediately Invoked Function Expression)를 사용할 수 있습니다.

for (var i = 0; i < 3; i++) {
  (function(i) {
    setTimeout(function() {
      console.log(i);
    }, 1000);
  })(i);
}

// 0, 1, 2 (1초 후에 각각 출력)

위 예제에서 IIFE는 각 반복마다 i 값을 인자로 받아 클로저를 생성합니다. 이 클로저는 각 i 값을 별도의 렉시컬 환경에 저장하여 올바른 값을 출력합니다.

4. 클로저의 메모리 관리

클로저는 강력하지만, 메모리 관리에 주의해야 합니다. 클로저가 참조하는 변수가 많아질수록 메모리 사용량이 증가할 수 있습니다. 따라서 불필요한 클로저는 생성하지 않도록 주의해야 합니다.

function heavyClosure() {
  const largeArray = new Array(1000000).fill('data');

  return function() {
    console.log(largeArray[0]);
  };
}

const heavy = heavyClosure();
// 클로저가 largeArray를 계속 참조하여 메모리 사용량 증가

위 예제에서 heavyClosure 함수는 큰 배열을 클로저로 유지하여 메모리를 많이 사용합니다. 불필요한 클로저는 사용 후에 참조를 제거하여 메모리 누수를 방지해야 합니다.

function createCounter() {
  let count = 0;

  function increment() {
    count++;
    console.log(count);
  }

  function decrement() {
    count--;
    console.log(count);
  }

  function getCount() {
    return count;
  }

  return { increment, decrement, getCount };
}

const counter = createCounter();
counter.increment(); // 1
counter.increment(); // 2
counter.decrement(); // 1
console.log(counter.getCount()); // 1

5. 클로저와 this 키워드

클로저와 this 키워드의 동작을 이해하는 것도 중요합니다. 클로저 내부에서 this 키워드를 사용할 때, 클로저가 생성된 위치의 this를 참조합니다.

const obj = {
  value: 10,
  createFunction: function() {
    return function() {
      console.log(this.value);
    };
  }
};

const func = obj.createFunction();
func(); // undefined (전역 객체의 this 참조)

const boundFunc = obj.createFunction().bind(obj);
boundFunc(); // 10 (obj의 this 참조)

위 예제에서 createFunction이 반환하는 클로저는 this를 전역 객체로 참조합니다. bind 메서드를 사용하여 thisobj로 바인딩할 수 있습니다.

6. 클로저의 한계와 주의점

클로저는 매우 유용하지만, 몇 가지 한계와 주의점이 있습니다.

  1. 메모리 누수: 클로저가 참조하는 변수가 많아질수록 메모리 사용량이 증가할 수 있습니다.
  2. 디버깅 어려움: 클로저를 사용한 코드의 디버깅이 어려울 수 있습니다.
  3. 성능 문제: 불필요한 클로저 생성은 성능 문제를 일으킬 수 있습니다.

클로저는 자바스크립트의 중요한 개념으로, 이를 잘 활용하면 코드의 재사용성과 유지보수성을 크게 향상시킬 수 있습니다. 클로저를 올바르게 이해하고 사용하면 더욱 효율적이고 강력한 자바스크립트 코드를 작성할 수 있습니다.


Leave a Reply

Your email address will not be published. Required fields are marked *