본문 바로가기

FE/JS

자바스크립트 - 클로저

클로저는 함수와 그 함수가 선언될 당시의 렉시컬 환경(Lexical Environment)에 대한 참조를 함께 저장하는 구조이다.

내부 함수가 실행될 때, 이 참조를 통해 자신이 선언될 당시 주변 스코프의 변수들에 접근할 수 있으며,

외부 함수의 실행이 완료된 후에도 해당 변수들이 메모리에 남아있어 접근이 가능하다.

 

더보기

렉시컬 환경

"함수나 블록이 선언될 때, 그 위치에서 접근 가능한 변수들이 담긴 객체"

 

✅ 렉시컬 환경의 구성

1️⃣ 환경 레코드 (Environment Record)

  • 현재 스코프(블록/함수 등)에서 선언된 변수(예: var, let, const)를 저장하는 객체.

2️⃣ 외부 렉시컬 환경에 대한 참조 (Outer Lexical Environment Reference)

  • 상위(외부) 스코프의 렉시컬 환경을 가리키는 참조.

클로저의 개념을 잘못 사용한 예시 

함수 안에서 선언한 변수는 함수 바깥에서 접근할 수 없다.

function inner() {
  console.log(count)
}

function outer() {
  let count = 0;
  inner()
}

inner(); // ReferenceError: count is not defined

 

클로저 예시 1

n0 = 'n0';
var v0 = 'v0';
let l0 = 'l0';
const c0 = 'c0';

console.log(v0, n0, l0, c0); // v0 n0 l0 c0
console.log(window.v0, window.n0, window.l0, window.c0); // v0 n0 undefined undefined

function fn1() {
  n1 = 'n1';
  var v1 = 'v1';
  let l1 = 'l1';
  const c1 = 'c1';

  return function fn2() {
    n2 = 'n2';

    console.log(n0, n1, n2); // n0 n1 n2
    var v2 = 'v2';
    console.log(v0, v2); // v0 v2
    console.log(v1);     // v1

    let l2 = 'l2';
    console.log(l0, l2); // l0 l2
    console.log(l1);     // l1

    const c2 = 'c2';
    console.log(c0, c2); // c0 c2
    console.log(c1);     // c1
  }
}

let f2 = fn1();
f2();

f2 = null;

1️⃣ fn1이 종료되어도, fn2가 fn1의 지역변수에 접근 가능 [ 클로저가 접근 가능한 변수들을 객체에 저장하기 때문에 ]
2️⃣ 이때, 그 “렉시컬 환경”은 heap 같은 공간에 살아있음
3️⃣ f2=null로 참조를 없애면 → 가비지 컬렉션으로 heap에서 제거됨

 

클로저 예시 2

function makeAdder(x) {
  return function (y) {
    return x + y;
  };
}

const add5 = makeAdder(5);
const add10 = makeAdder(10);

console.log(add5(2));  // 7
console.log(add10(2)); // 12

makeAdder함수를 반환하는 함수다.

두 함수는 각자 독립적인 클로저로, 자신만의 x 값을 heap에 보관한다.

GC 관점에서는 add5, add10이 참조를 잃으면 heap에서 정리된다.


렉시컬 스코프

자바스크립트는 함수를 어디서 호출했는지가 아니라, 어디서 정의했는지를 기준으로 변수 접근 범위를 결정한다.

function outer() {
  let x = 2;
  function inner() {
    console.log(x);
  }
  return inner;
}

const fn = outer();
fn(); // 2

이러한 개념을 렉시컬 스코프, 또는 정적 스코프라고 한다.

실행 중에 결정되는 게 아니라 코드 작성 시점 기준이다.


성능 관련 고려사항

모든 클로저가 항상 필요한 건 아니다.

무심코 내부에 함수를 계속 만들면, 그만큼 메모리도 차지하고 성능에도 영향을 준다.

예를 들어 생성자 함수 안에 메서드를 직접 만들면, 인스턴스가 생길 때마다 그 함수도 새로 만들어진다:

function MyObject(name, message) {
  this.name = name;
  this.message = message;

  this.getName = function () {
    return this.name;
  };

  this.getMessage = function () {
    return this.message;
  };
}

 

클로저가 필요 없다면 아래처럼 prototype을 쓰는 게 더 낫다:

function MyObject(name, message) {
  this.name = name;
  this.message = message;
}

MyObject.prototype.getName = function () {
  return this.name;
};

MyObject.prototype.getMessage = function () {
  return this.message;
};

이렇게 하면 함수는 한 번만 정의되고, 인스턴스들이 공유해서 쓴다. 메모리 낭비도 없고 성능도 낫다.

 

클로저를 유용하게 사용하는 방법

https://developer.mozilla.org/ko/docs/Web/JavaScript/Guide/Closures#실용적인_클로저


참고자료

'FE > JS' 카테고리의 다른 글

JavaScript - this  (0) 2025.05.19
자바스크립트 실행 컨텍스트  (0) 2025.05.19
자바스크립트 호이스팅 정리  (1) 2025.05.18
null, undefined, undeclared, NaN  (1) 2025.05.17
JavaScript: var, let, const  (0) 2025.05.13