[웹 개발 기초 자바스크립트] 15. 자바스크립트 Closure

Shy·2023년 8월 31일
0

NodeJS(Express&Next.js)

목록 보기
17/39

Closure

클로저(Closure)는 자바스크립트의 중요한 개념 중 하나로, 함수와 함수가 선언된 어휘적 환경(Lexical Environment)의 조합이다. 간단히 말하자면, 클로저는 함수 내부에서 생성되는 지역 변수를 함수 외부에서도 계속해서 접근할 수 있게 해주는 기능이다.

function outer() {
  let outerVar = 'I am from outer function!';
  
  function inner() {
    console.log(outerVar); // inner 함수는 외부 함수 outer의 변수 outerVar에 접근 가능
  }
  
  return inner;
}

const myClosure = outer();
myClosure();  // "I am from outer function!"이 출력됨

위 예시에서 outer 함수는 inner 함수를 반환한다. inner 함수는 outer 함수의 변수 outerVar에 접근한다. outer 함수가 종료된 이후에도, myClosure를 통해 inner 함수를 호출하면 outerVar에 여전히 접근이 가능하다. 이것이 바로 클로저의 작동 원리이다.

클로저의 활용

클로저는 여러 분야에서 활용된다. 몇 가지 예를 들자면,

  1. 데이터 은닉(Data Encapsulation): 클로저를 사용하면 특정 변수나 데이터를 함수 외부에서 직접 수정할 수 없게 만들 수 있다.
  2. 동적 함수 생성: 실행 시점에 새로운 함수를 만들고, 그 함수가 다른 함수나 변수에 접근할 수 있게 할 수 있다.
  3. 콜백과 비동기 작업: 비동기 API 호출이나 이벤트 핸들러에서 원래의 컨텍스트에 접근해야 할 때 클로저를 사용할 수 있다.

주의사항

클로저는 강력한 기능이지만, 부주의하게 사용하면 메모리 누수를 일으킬 수 있다. 클로저로 참조되는 외부 변수는 가비지 컬렉션(Garbage Collection)의 대상이 되지 않으므로, 필요없는 클로저는 명시적으로 제거해주어야 한다.

클로저는 자바스크립트의 핵심 메커니즘 중 하나로, 이해하고 잘 활용하면 다양한 고급 프로그래밍 패턴을 구현할 수 있습니다.

예제

function outerFunction(outerVariable) { // 외부 함수
  return function innerFunction(innerVariable) { // 내부 함수
    console.log('Outer Variable: ' + outerVariable)
    console.log('Inner Variable: ' + innerVariable)
  }
}

const newFunction = outerFunction('outside'); // 함수가 함수를 리턴한다. 그래서 newFunction변수는 함수이다.
newFunction('inside');

코드 분석

이 코드는 클로저를 이용한 간단한 예제다. 함수 outerFunction이 있고, 이 함수는 또 다른 함수 innerFunction을 반환한다. innerFunction은 outerVariable이라는 변수에 접근하는 것을 보여준다.

코드 실행 순서

  1. outerFunction을 호출하면서 'outside'라는 문자열을 인자로 넘긴다.
  2. outerFunction은 innerFunction을 반환하고, 이는 newFunction에 저장된다.
  3. newFunction('inside')를 호출하면, 이전에 반환된 innerFunction이 실행된다.

클로저의 역할

innerFunction은 outerVariable과 innerVariable에 모두 접근할 수 있다.
outerFunction의 실행이 끝난 후에도 innerFunction은 outerVariable에 접근할 수 있다. 이것이 바로 클로저의 특징이다.

출력 결과

  • 'Outer Variable: outside': outerFunction의 매개변수로 전달된 'outside'가 출력된다.
  • 'Inner Variable: inside': newFunction (즉, innerFunction)의 매개변수로 전달된 'inside'가 출력된다.

따라서, 이 코드를 실행하면 다음과 같은 출력을 볼 수 있습니다.

// Outer Variable: outside
// Inner Variable: inside

이 예제는 클로저가 어떻게 작동하는지를 잘 보여준다. 내부 함수가 외부 함수의 지역 변수에 접근할 수 있으며, 외부 함수가 종료된 이후에도 그 값을 유지한다.

closure 메모리 누수

클로저를 부주의하게 사용하면 메모리 누수를 일으킬 수 있다. 클로저는 외부 함수의 변수에 대한 참조를 유지하기 때문에, 이 변수들이 메모리에서 해제되지 않는다. 이런 현상은 특히 다음과 같은 상황에서 문제가 될 수 있다.

  1. 무한 루프 또는 재귀: 클로저가 무한 루프나 재귀에 연결되어 있으면, 이 클로저가 참조하는 외부 변수들이 점점 쌓여만 간다.
  2. DOM 참조: 클로저가 DOM 요소를 참조하고 있을 때 해당 DOM 요소가 문서에서 삭제되어도 클로저에 의해 참조가 유지되면 메모리 누수가 발생할 수 있다.
  3. 데이터 구조와 클로저: 큰 데이터 구조를 참조하는 클로저가 있고, 이 클로저가 계속해서 메모리에 남아있으면 그 데이터 구조도 메모리에 계속 남아있게 된다.
  4. 이벤트 리스너: 클로저를 이용한 이벤트 리스너를 적절히 해제하지 않으면, 해당 클로저는 계속 메모리에 남게 된다.

이러한 이유로 인해 클로저를 사용할 때는 메모리 관리를 주의 깊게 해야 한다. 필요 없어진 객체나 변수, 함수 등에 대한 참조를 명확히 끊어주는 것이 좋다.

예제

예제1

let a = 'a';

function functionA() {
  let b = 'b';
  console.log(a, b);
}

functionA();

코드분석

  1. let a = 'a';: 이 코드는 변수 a를 선언하고 문자열 'a'로 초기화한다. 이 변수는 전역 변수로, 스크립트의 어디에서든 접근할 수 있다.

  2. function functionA() { ... }: 이 부분은 functionA라는 이름의 함수를 선언한다. 이 함수는 내부에서 변수 b를 선언하고, console.log(a, b);를 호출하여 변수 a와 b의 값을 출력한다.

  3. let b = 'b';: 함수 functionA의 내부에서 변수 b를 선언하고 문자열 'b'로 초기화한다. 이 변수는 functionA 함수의 지역 변수이다. 이 변수는 함수가 호출되는 동안에만 존재하고, 함수의 실행이 끝나면 사라진다.

  4. console.log(a, b);: console.log 함수를 호출하여 전역 변수 a와 지역 변수 b의 값을 콘솔에 출력한다. 출력은 'a b'로 나타날 것이다.

  5. functionA();: 마지막으로, functionA 함수를 호출한다. 이 함수의 호출로 인해 위에서 설명한 동작들이 실행된다.

이 코드의 핵심은 변수의 스코프이다. a는 전역 스코프에 있어서 함수 내부에서도 접근할 수 있다. 반면에 b는 지역 스코프에 있어서 함수 functionA 내부에서만 접근할 수 있다.

예제2

let a = 'a';
function functionB() {
  let c = 'c'
  console.log(a, b, c) // b로 인해 오류 발생!
}

function functionA() {
  let b = 'b';
  console.log(a, b);
  functionB();
}

코드 분석

  1. let a = 'a';: 전역 스코프에서 변수 a를 선언하고 'a'로 초기화한다. 이 변수는 스크립트 전체에서 접근할 수 있다.

  2. function functionB() { ... }: functionB라는 이름의 함수를 선언한다. 이 함수는 내부에서 변수 c를 선언하고, console.log(a, b, c)를 호출하여 변수 a, b, c의 값을 출력하려고 한다.

  3. let c = 'c': 함수 functionB의 내부에서 지역 변수 c를 선언하고 'c'로 초기화한다. 이 변수는 functionB 내부에서만 접근 가능하다.

  4. function functionA() { ... }: functionA라는 이름의 또 다른 함수를 선언한다. 이 함수는 내부에서 변수 b를 선언하고, console.log(a, b);를 호출하여 변수 a와 b의 값을 출력한다. 그 다음에 functionB();를 호출한다.

  5. let b = 'b': 함수 functionA의 내부에서 지역 변수 b를 선언하고 'b'로 초기화한다. 이 변수는 functionA 내부에서만 접근 가능하다.

  6. functionA();: functionA 함수를 호출한다.

이 코드를 실행하면 다음과 같은 문제가 발생할 것이다

functionA는 정상적으로 실행되고 콘솔에 'a b'를 출력한다.
그러나 functionA 내부에서 functionB를 호출하면, functionB는 변수 b에 접근할 수 없기 때문에 에러가 발생한다. 이는 b가 functionA의 지역 스코프 내에 있기 때문이다.
즉, 이 코드는 "ReferenceError: b is not defined"와 같은 오류 메시지를 출력할 것이다.

예제2 fix

Closure를 이용해서 해결할 수 있다.

let a = 'a'
function functionA() {
  function functionB() { // functionB선언
    let c = 'c';
    console.log(a, b, c);
  }
  let b = 'b'
  console.log(a, b);
  functionB(); // functionB호출
}

functionA();

다른 함수 내부에 정의된 함수(innerFunction)가 있는 경우, 외부 함수(outerFunction)가 실행을 완료하고 해당 변수가 해당 함수 외부에서 더 이상 엑세스할 수 없는 경우에도 해당 내부 함수는 외부 함수의 변수 및 범위에 엑세스할 수 있다.

코드분석

이 자바스크립트 코드 예제에서는 스코프, 클로저, 그리고 함수 호출에 대한 몇 가지 중요한 개념을 다룬다.

  1. let a = 'a';: 전역 스코프에서 변수 a를 선언하고 'a'로 초기화한다. 이 변수는 스크립트 전체에서 접근할 수 있다.

  2. function functionA() { ... }: functionA라는 이름의 함수를 선언한다. 이 함수는 내부에서 변수 b를 선언하고, console.log(a, b);를 호출하여 변수 a와 b의 값을 출력한다. 그 다음에 functionB();를 호출한다.

  3. function functionB() { ... }: functionA의 내부에서 functionB라는 또 다른 함수를 선언한다. 이 함수는 내부에서 변수 c를 선언하고, console.log(a, b, c);를 호출하여 변수 a, b, c의 값을 출력한다.

  4. let b = 'b';: 함수 functionA의 내부에서 지역 변수 b를 선언하고 'b'로 초기화한다. 이 변수는 functionA 및 functionA 내부의 functionB에서 접근 가능하다. (클로저 때문에)

  5. let c = 'c';: 함수 functionB의 내부에서 지역 변수 c를 선언하고 'c'로 초기화한다. 이 변수는 functionB 내부에서만 접근 가능하다.

  6. functionA();: functionA 함수를 호출한다.

코드를 실행하면 다음과 같은 출력이 발생한다.

  1. functionA가 호출되면 콘솔에 'a b'를 출력한다.
  2. functionA 내부에서 functionB가 호출되고 콘솔에 'a b c'를 출력한다.

이 코드는 에러 없이 실행되고 원하는대로 동작한다. 이는 functionB가 functionA의 스코프에 클로저를 형성하기 때문에 functionA의 지역 변수 b에 접근할 수 있기 때문이다.

profile
초보개발자. 백엔드 지망. 2024년 9월 취업 예정

0개의 댓글