Skip to main content

JavaScript 클로저 완전 정복 – 초보자를 위한 함수 기억하기 비법 (55자)

Published 4 min readMarkdown

# 서론 – “왜 함수가 기억을 할까?”

프로그래밍을 처음 시작한 사람이라면 “함수가 호출될 때마다 새로운 메모리를 할당한다”는 설명을 들어본 적이 있을 겁니다. 그런데 JavaScript에서는 함수가 자신이 선언됐던 환경을 기억하고, 그 기억을 바탕으로 동작한다는 사실, 알고 계셨나요?

이 현상을 제대로 이해하지 못하면 비동기 처리, 콜백, 모듈화 등 실무에서 마주치는 수많은 버그에 허덕이게 됩니다. 이번 글에서는 클로저(Closure) 라는 개념을 쉽게 풀어내고, 실제 예제와 함께 함수가 어떻게 기억을 하는지를 단계별로 살펴보겠습니다.

키워드: JavaScript 클로저, 함수 스코프, 프로그래밍 기초


# 1️⃣ 클로저란 무엇인가? – 기본 개념 정리

# 1-1. 스코프와 렉시컬 스코프

  • 스코프(scope) : 변수와 함수가 접근 가능한 범위

  • 렉시컬(Lexical) 스코프 : 함수가 정의된 위치에 따라 스코프가 결정

JavaScript는 정적 스코프를 사용합니다. 즉, 함수가 어디서 호출되었는가가 아니라 어디서 정의되었는가에 따라 변수에 접근할 수 있는 범위가 정해집니다.

# 1-2. 클로저 정의

클로저는 함수가 선언된 렉시컬 환경(외부 변수들)을 함께 기억하고, 그 환경에 접근할 수 있는 함수와 그 환경의 조합을 말합니다.

쉽게 말해, 함수가 살아있는 동안 자신이 선언된 스코프를 “포획”하고, 그 포획된 스코프를 통해 변수에 접근하는 것이 클로저입니다.

# 1-3. 왜 클로저가 필요한가?

  • 데이터 은닉 : 외부에서 직접 접근할 수 없는 비공개 변수 생성

  • 상태 유지 : 함수 호출 사이에 값을 유지(예: 카운터)

  • 함수 팩토리 : 매개변수에 따라 다른 동작을 하는 함수를 동적으로 생성


# 2️⃣ 클로저의 작동 원리 – 단계별 시각화

# 2-1. 실행 컨텍스트와 변수 환경 레코드

  1. 함수 선언 → 실행 컨텍스트가 생성되고, 변수 환경 레코드(Variable Environment Record) 가 만든다.

  2. 함수가 호출될 때마다 새로운 실행 컨텍스트가 스택에 쌓인다.

  3. 함수가 종료되면 실행 컨텍스트는 사라지지만, 클로저가 참조하고 있는 변수 환경 레코드는 가비지 컬렉션되지 않는다.

# 2-2. 예제: 간단한 카운터 만들기

javascript

function makeCounter() {

  let count = 0;               // 외부 변수

  return function() {         // 내부 함수 (클로저)

    count += 1;

    return count;

  };

}

const counter = makeCounter(); // makeCounter 실행 → 내부 함수 반환

console.log(counter()); // 1

console.log(counter()); // 2
  • makeCounter 실행이 끝나도 count 변수는 소멸되지 않는다.

  • 반환된 내부 함수는 count를 포획하고 있기 때문에, 매 호출 시 이전 값을 기억한다.

# 2-3. 클로저와 메모리 관리

  • 클로저가 불필요하게 큰 객체를 포획하면 메모리 누수가 발생할 수 있다.

  • 사용이 끝난 클로저는 null 로 할당하거나, 스코프를 제한해 메모리를 해제한다.


# 3️⃣ 실전 예제 – 비동기와 콜백에서 클로저 활용하기

# 3-1. 타이머와 클로저

javascript

function startTimer(seconds) {

  let remaining = seconds;

  const id = setInterval(function() {

    remaining--;

    console.log(`남은 시간: ${remaining}s`);

    if (remaining <= 0) clearInterval(id);

  }, 1000);

}

startTimer(5);
  • setInterval 내부 콜백은 remaining 변수를 클로저로 포획한다.

  • 타이머가 진행되는 동안 remaining 값은 지속적으로 업데이트된다.

# 3-2. 이벤트 리스너와 클로저

javascript

function attachClickHandler(btnId) {

  let clickCount = 0;

  document.getElementBy **getElementById**(btnId).addEventListener('click', function() {

    clickCount++;

    console.log(`버튼 클릭 횟수: ${clickCount}`);

  });

}
attachClickHandler('myBtn');
  • 클릭 이벤트 콜백이 clickCount를 기억하므로, 버튼을 누를 때마다 누적된 횟수를 출력한다.

# 3-3. 클로저를 이용한 모듈 패턴

javascript

const TodoModule = (function() {

  let todos = []; // 비공개 변수

  return {

    add: function(item) { todos.push(item); },

    list: function() { return [...todos]; }

  };

})();

TodoModule.add('Learn JavaScript');

console.log(TodoModule.list()); // ['Learn JavaScript']
  • 즉시 실행 함수(IIFE) 안에 비공개 데이터 todos를 두고, 외부에 공개된 메서드만 반환한다.

  • 이 패턴은 데이터 은닉과 API 제공을 동시에 만족한다.


# 4️⃣ 클로저 흔히 하는 실수와 해결법

| 흔한 실수 | 원인 | 해결 방법 |

|----------|------|-----------|

| 루프 안에서 클로저 사용 시 변수 공유 | var 로 선언된 변수는 함수 스코프라서 마지막 값만 기억 | let 사용하거나 즉시 실행 함수를 활용 |

| 메모리 누수 | 클로저가 큰 DOM 객체 등을 포획 | 필요 없는 참조는 null 로 초기화, 혹은 스코프 최소화 |

| 디버깅 어려움 | 클로저 내부 변수는 콘솔에 바로 보이지 않음 | 브라우저 DevTools의 Closure Scope 확인 기능 활용 |

javascript

// var 로 인한 문제 예시

for (var i = 0; i < 3; i++) {

  setTimeout(function() {

    console.log(i); // 3, 3, 3

  }, 100);

}

// let 로 해결

for (let i = 0; i < 3; i++) {

  setTimeout(function() {

    console.log(i); // 0, 1, 2

  }, 100);

}

# 5️⃣ 클로저와 최신 JavaScript (ES6+)

  • arrow function 은 this 바인딩이 lexical이지만, 클로저와는 별개이다.

  • module (ES6 import/export) 은 내부적으로 클로저를 활용해 프라이빗 스코프를 만든다.

  • async/await 와 Promise 도 콜백 대신 클로저를 이용해 비동기 흐름을 관리한다.

javascript

// async 함수 안에서 클로저 활용

async function fetchData(url) {

  const response = await fetch(url);

  const data = await response.json();

  return function() {

    console.log('받은 데이터:', data); // data는 클로저에 포획

  };

}
const logData = await fetchData('/api/items');

logData(); // 콘솔에 데이터 출력

# 결론 – 지금 바로 클로저를 활용해 보세요!

클로저는 JavaScript 프로그래밍 기초 중에서도 가장 강력하면서도 헷갈리기 쉬운 개념입니다.

하지만 위에서 살펴본 스코프 이해 → 변수 환경 포획 → 실전 적용 순서를 따라가면, 어느새 데이터 은닉, 상태 유지, 비동기 제어 등 다양한 상황에서 클로저를 자연스럽게 활용하고 있을 겁니다.

# 📌 행동 유도

  1. 직접 실습 – 오늘 배운 카운터 예제를 콘솔에 입력하고 동작을 확인해 보세요.

  2. 코드 리팩터링 – 기존 프로젝트에 콜백이 많은 부분을 클로저 기반 모듈로 바꿔 보세요.

  3. 블로그 공유 – 이 글이 도움이 되었다면 SNS에 공유하고, #JavaScript클로저 해시태그와 함께 의견을 남겨 주세요!

클로저를 제대로 이해하고 활용한다면, 복잡한 로직도 깔끔하게 관리하고 버그를 크게 줄일 수 있습니다. 이제 여러분도 JavaScript의 비밀스러운 기억력을 손에 넣으세요! 🚀

No comments yet. Be the first to comment.

More from this blog