6.2.1 클로저가 뭐예요? - 기억하는 특별한 함수
📘 6.2.1 클로저가 뭐예요? - 기억하는 특별한 함수
지난 시간까지 우리는 범위의 연결고리와 변수를 찾아가는 조용한 규칙들을 배웠습니다. 자바스크립트가 마치 세심한 탐정처럼 체계적으로 변수를 찾아가는 과정을 함께 따라갔죠.
오늘은 이런 범위의 힘을 바탕으로 한 정말 신기하고 특별한 개념을 만나보겠습니다. 바로 클로저라는 것인데요, 이는 함수가 마치 오래된 나무처럼 자신의 환경을 깊이 기억하는 놀라운 능력입니다. 함께 이 흥미로운 여행을 떠나보겠습니다.
🧠 새로운 단어들과 친해지기
클로저를 이해하는 데 도움이 되는 단어들을 알아보겠습니다.
| 단어 | 의미 |
|---|---|
| 클로저 (closure) | 함수가 자신이 만들어진 환경을 기억하는 특별한 능력입니다. 마치 소중한 기억을 간직하는 것과 같습니다. |
| 환경 | 함수가 만들어질 때의 주변 상황과 변수들을 의미합니다. 마치 사진 속에 담긴 배경처럼요. |
| 바깥 변수 | 함수 바깥에서 만들어졌지만 함수 안에서 사용하는 변수입니다. 마치 이웃에서 빌려온 물건 같습니다. |
| 함수 공장 | 비슷한 기능을 가진 여러 함수를 만들어내는 함수입니다. 마치 같은 디자인으로 여러 제품을 만드는 공방 같습니다. |
| 정보 보호 | 중요한 정보를 밖에서 직접 접근할 수 없도록 보호하는 것입니다. 마치 안전한 서랍장처럼요. |
'클로저(closure)'라는 말은 영어로 "닫힌다, 감싸다"라는 뜻입니다. 마치 소중한 것을 품에 안고 보호하는 것처럼, 함수가 자신의 환경을 꼭 껴안고 기억한다는 의미에서 이름이 붙었습니다.
✨ 클로저, 그것은 무엇일까요?
클로저는 자바스크립트의 가장 신기하고 아름다운 기능 중 하나입니다. 이 기능을 이해하면 마치 프로그래밍의 깊은 세계로 들어간 것 같은 느낌을 받게 됩니다.
클로저의 가장 놀라운 점은 함수가 끝난 후에도 그 함수의 변수들을 계속 기억한다는 것입니다. 보통 함수가 끝나면 그 안의 변수들은 사라져버리잖아요? 하지만 클로저는 마치 소중한 추억을 간직하는 것처럼 그 순간의 환경을 그대로 보존해둡니다.
예를 들어, 할머니가 여러분에게 특별한 요리법을 알려주시고 멀리 이사를 가셨다고 해보세요. 할머니는 계시지 않지만, 여러분은 여전히 그 소중한 요리법을 기억하고 있어서 언제든지 따뜻한 요리를 만들 수 있습니다. 클로저도 이와 같습니다.
또한 클로저는 정보를 안전하게 보호하는 역할도 합니다. 마치 조용한 서랍장처럼, 중요한 정보를 밖에서 함부로 바꿀 수 없도록 숨겨두고, 오직 특별한 방법으로만 접근할 수 있게 해줍니다.
마음속 이야기: 개인 서랍장 시스템
클로저를 더 쉽게 이해하기 위해 '개인 서랍장' 이야기를 들어보겠습니다.
학교에서 여러분은 개인 서랍장을 하나씩 받았습니다. 이 서랍장은 참 특별합니다.
먼저 선생님이 여러분만의 서랍장을 준비해줍니다. 그 안에는 여러분의 이름, 학급, 좋아하는 과목 등 개인적인 정보들과 소중한 물건들을 넣어둡니다. 그다음 특별한 열쇠를 하나 만들어서 여러분에게 건네줍니다.
이제 선생님은 다른 일을 보러 가시지만, 놀랍게도 여러분의 열쇠는 여전히 그 서랍장의 모든 물건들에 접근할 수 있습니다.
더 신기한 것은, 철수의 열쇠는 철수의 서랍장만 열 수 있고, 영희의 열쇠는 영희의 서랍장만 열 수 있다는 점입니다. 서로 섞이지 않습니다.
그래서 철수가 자신의 열쇠로 서랍장을 열면 "철수의 수학 노트"를 꺼낼 수 있고, 영희가 자신의 열쇠로 서랍장을 열면 "영희의 미술 도구"를 꺼낼 수 있습니다.
이 개인 서랍장 시스템이 바로 클로저이고, 선생님이 바깥쪽 함수, 개인 서랍장이 각 함수의 고유한 환경, 열쇠가 클로저 함수입니다. 선생님이 가신 후(함수가 끝난 후)에도 열쇠는 여전히 서랍장의 모든 것을 기억하고 사용할 수 있는 것입니다.
🎯 왜 클로저를 배워야 할까요?
그렇다면 우리는 왜 클로저를 배워야 할까요? 여러 소중한 이유들이 있습니다.
첫째, 정보를 안전하게 보호할 수 있습니다. 은행의 금고처럼, 중요한 정보를 밖에서 함부로 건드릴 수 없도록 숨겨두고, 오직 허가받은 방법으로만 접근할 수 있게 만들 수 있습니다.
둘째, 비슷한 기능을 가진 여러 함수를 쉽게 만들 수 있습니다. 마치 숙련된 공예가가 같은 기법으로 여러 작품을 만드는 것처럼, 하나의 틀로 여러 개의 독립적인 함수를 만들어낼 수 있습니다.
셋째, 함수가 특별한 능력을 기억하게 할 수 있습니다. 예를 들어, 각자 다른 이름을 기억하는 인사 함수나, 각자 다른 계산 방식을 기억하는 함수를 만들 수 있습니다.
마지막으로, 고급 프로그래밍 기법들의 기초가 됩니다. 많은 현대적인 자바스크립트 기법들이 클로저를 바탕으로 만들어져 있어서, 클로저를 이해하면 더 아름다운 프로그램을 만들 수 있습니다.
⚙️ 기본 사용법 배우기
클로저는 특별한 문법이 따로 있는 것은 아닙니다. 대신 함수 안에 함수를 만들고, 안쪽 함수를 바깥으로 반환하는 방법으로 만들어집니다.
🔹 기본 클로저 구조
function 바깥쪽함수(받을값) {
// 바깥쪽 함수의 변수들 (클로저가 기억할 환경)
let 기억할변수 = 값;
// 안쪽 함수 (클로저가 될 함수)
function 안쪽함수() {
// 바깥쪽 변수를 사용 (클로저 발생!)
return 기억할변수;
}
// 안쪽 함수를 반환 (중요!)
return 안쪽함수;
}
// 클로저 만들고 사용하기
let 클로저함수 = 바깥쪽함수(값);
클로저함수(); // 바깥쪽함수가 끝났지만 여전히 변수에 접근 가능!
🔹 클로저 동작 단계
클로저가 만들어지고 사용되는 과정을 단계별로 살펴보겠습니다.
1단계: 바깥쪽 함수를 부르면서 실행 환경을 만듭니다.
2단계: 바깥쪽 함수 안에 변수들을 만듭니다.
3단계: 안쪽 함수가 바깥쪽 변수들을 참조합니다.
4단계: 안쪽 함수를 반환하여 클로저가 생성됩니다.
5단계: 바깥쪽 함수가 종료됩니다.
6단계: 클로저 함수를 실행할 때 여전히 바깥쪽 변수에 접근할 수 있습니다.
🎯 5단원 복습 문제 - 함수 연습하기
6단원을 배우기 전에 5단원에서 배운 함수를 복습해보겠습니다. 함수의 반환값과 중첩 함수는 클로저를 만드는 기본 요소입니다.
과거의 길을 되돌아 우리가 배운 함수들을 다시 한 번 들여다보는 것은, 마치 오래된 일기를 다시 읽는 것과 같습니다. 그 안에는 우리가 함께 쌓아온 배움의 흔적이 고스란히 남아 있습니다.
복습 문제 1: 함수의 반환값 이해하기
// 함수가 값을 반환하는 것과 함수 자체를 반환하는 것의 차이
function getValue() {
let message = "안녕하세요!";
return message; // 값 자체를 반환
}
function getFunction() {
let message = "안녕하세요!";
function sayHello() {
return message; // 바깥쪽 변수 사용
}
return sayHello; // 함수 자체를 반환 (클로저의 기초!)
}
// 사용 방법 비교하기
console.log("=== 함수 반환값 복습하기 ===");
let result1 = getValue(); // 값을 받기
let result2 = getFunction(); // 함수를 받기
console.log("값 반환:", result1); // "안녕하세요!" 직접 출력
console.log("함수 반환:", result2()); // 함수를 실행해서 "안녕하세요!" 출력
해답과 설명: getValue()는 문자열 값을 반환하지만, getFunction()은 함수 자체를 반환합니다. 반환된 함수는 나중에 실행할 수 있고, 이때 바깥쪽 함수의 변수를 여전히 사용할 수 있습니다. 이것이 클로저의 기본 원리입니다.
복습 문제 2: 중첩 함수에서 변수 공유하기
// 중첩 함수가 바깥쪽 변수를 어떻게 사용하는지 확인하기
function createPersonalGreeting(name) {
let personName = name; // 바깥쪽 함수의 변수
let greetingCount = 0; // 인사 횟수를 기록할 변수
console.log(personName + "님을 위한 인사 함수를 만들었어요!");
function greet() {
greetingCount = greetingCount + 1; // 바깥쪽 변수를 수정
let greeting = "안녕하세요, " + personName + "님! (" + greetingCount + "번째 인사)";
console.log(greeting);
return greeting;
}
return greet; // 안쪽 함수를 반환
}
// 개인 인사 함수 테스트하기
console.log("=== 중첩 함수 복습하기 ===");
let greetAlice = createPersonalGreeting("앨리스"); // 앨리스용 인사 함수 만들기
let greetBob = createPersonalGreeting("밥"); // 밥용 인사 함수 만들기
greetAlice(); // 앨리스에게 첫 번째 인사
greetAlice(); // 앨리스에게 두 번째 인사
greetBob(); // 밥에게 첫 번째 인사 (독립적으로 작동)
해답과 설명: 안쪽 함수 greet는 바깥쪽 함수의 변수 personName과 greetingCount를 자유롭게 사용할 수 있습니다. 각각의 인사 함수는 자신만의 이름과 인사 횟수를 독립적으로 기억하고 관리합니다. 이것이 클로저가 각자 다른 환경을 기억하는 모습입니다.
🧪 직접 해보면서 배우기
이제 실제 예제들을 통해 클로저가 어떻게 동작하는지 차근차근 살펴보겠습니다.
🔹 예제 1: 개인 비밀 상자 만들기
첫 번째 예제에서는 클로저를 사용해서 비밀번호로 보호되는 개인 비밀 상자를 만들어보겠습니다.
// Ex1) 비밀번호로 보호되는 개인 비밀 상자를 만들어서 소중한 물건을 안전하게 보관해보자!
// 개인 비밀 상자 만들기 (클로저 활용)
function createSecretBox(secretPassword) {
// 상자 안의 소중한 물건들 (밖에서 직접 접근 불가!)
let diary = "나의 비밀 일기"; // 비밀 일기 저장하기
let money = "용돈 5000원"; // 용돈 저장하기
let photo = "가족 사진"; // 가족 사진 저장하기
console.log("🔒 개인 비밀 상자가 만들어졌습니다!");
console.log("🔑 비밀번호로 보호되고 있어요.");
// 상자를 여는 특별한 함수 (클로저!)
function openBox(inputPassword) {
if (inputPassword === secretPassword) { // 입력받은 비밀번호와 진짜 비밀번호 비교하기
console.log("✅ 비밀번호가 맞습니다! 상자가 열렸어요!"); // 비밀번호 맞을 때 성공 메시지 출력하기
console.log("📔 일기:", diary); // 비밀 일기 보여주기
console.log("💰 용돈:", money); // 용돈 보여주기
console.log("📷 사진:", photo); // 가족 사진 보여주기
return { // 모든 물건 정보를 담은 객체 반환하기
diary: diary,
money: money,
photo: photo
};
} else {
console.log("❌ 비밀번호가 틀렸습니다! 상자가 열리지 않아요."); // 비밀번호 틀릴 때 실패 메시지 출력하기
return "접근 거부됨"; // 접근 거부 메시지 반환하기
}
}
console.log("🗝️ 상자 열쇠를 드립니다!");
return openBox; // 클로저 함수 반환!
}
// 개인 비밀 상자 사용해보기
console.log("=== 개인 비밀 상자 실험 ===");
let myBox = createSecretBox("1234"); // 클로저 생성하기!
console.log("\n--- 첫 번째 시도 ---");
myBox("wrong"); // 틀린 비밀번호로 시도하기
console.log("\n--- 두 번째 시도 ---");
myBox("1234"); // 맞는 비밀번호로 시도하기
console.log("\n🎉 클로저가 비밀번호와 물건들을 기억하고 있어요!");
이 예제는 개인 비밀 상자를 보여줍니다. createSecretBox 함수가 끝난 후에도 openBox 함수는 여전히 비밀번호와 소중한 물건들을 기억하고 있습니다. 마치 상자가 자신의 비밀을 계속 간직하고 있는 것과 같습니다.
🔹 예제 2: 각자 다른 이름을 기억하는 인사 함수들
두 번째 예제에서는 클로저를 사용해서 각자 다른 이름을 기억하는 인사 함수들을 만들어보겠습니다.
// Ex2) 각자 다른 이름을 기억하는 개인 맞춤 인사 도우미를 만들어보자!
// 개인 맞춤 인사 함수 만들기 (클로저 활용)
function createPersonalHelper(personName) {
// 각 함수가 기억할 개인 정보
let name = personName; // 개인 이름 저장하기
let helpCount = 0; // 도움을 준 횟수 저장하기 (0부터 시작)
console.log(name + "님을 위한 도우미가 준비되었습니다!");
// 개인 맞춤 도우미 함수 (클로저!)
function personalHelper(task) {
helpCount = helpCount + 1; // 도움 횟수를 1 증가시키기
let response; // 응답 메시지를 저장할 변수 만들기
if (task === "숙제") { // 숙제 도움 요청인지 확인하기
response = name + "님, 숙제를 도와드릴게요!"; // 숙제 도움 응답 만들기
} else if (task === "청소") { // 청소 도움 요청인지 확인하기
response = name + "님, 청소를 도와드릴게요!"; // 청소 도움 응답 만들기
} else if (task === "요리") { // 요리 도움 요청인지 확인하기
response = name + "님, 요리를 도와드릴게요!"; // 요리 도움 응답 만들기
} else { // 다른 요청일 때
response = name + "님, 무엇을 도와드릴까요?"; // 기본 응답 만들기
}
console.log(response); // 만든 응답 출력하기
console.log("(" + name + "님께 " + helpCount + "번째 도움)"); // 도움 횟수 알려주기
return response; // 응답 반환하기
}
return personalHelper; // 클로저 함수 반환!
}
// 여러 사람을 위한 도우미 함수들 만들기
console.log("=== 개인 맞춤 도우미 실험 ===");
let helpAlice = createPersonalHelper("앨리스"); // 앨리스를 위한 도우미 만들기
let helpBob = createPersonalHelper("밥"); // 밥을 위한 도우미 만들기
let helpCharlie = createPersonalHelper("찰리"); // 찰리를 위한 도우미 만들기
console.log("\n--- 각자 다른 이름을 기억해요! ---");
helpAlice("숙제"); // 앨리스에게 숙제 도움 주기
helpBob("청소"); // 밥에게 청소 도움 주기
helpCharlie("요리"); // 찰리에게 요리 도움 주기
console.log("\n--- 도움 횟수도 각자 따로 기억해요! ---");
helpAlice("요리"); // 앨리스에게 2번째 도움 주기
helpAlice(""); // 앨리스에게 3번째 도움 주기
helpBob("숙제"); // 밥에게 2번째 도움 주기
console.log("\n🎯 각 함수가 자신만의 이름과 횟수를 기억하고 있어요!");
이 예제는 함수 공방의 개념을 보여줍니다. 같은 틀(createPersonalHelper)로 만들어졌지만, 각각의 클로저 함수는 서로 다른 이름과 도움 횟수를 독립적으로 기억하고 있습니다.
🔹 예제 3: 나만의 저금통 만들기
세 번째 예제에서는 클로저를 사용해서 각자 독립적인 저금통을 만들어보겠습니다.
// Ex3) 각자 독립적인 상태를 기억하는 개인용 저금통을 만들어보자!
// 개인용 저금통 만들기 (클로저 활용)
function createPiggyBank(ownerName) {
// 저금통이 기억할 상태들
let name = ownerName; // 저금통 주인 이름 저장하기
let totalMoney = 0; // 현재 저금된 돈 저장하기 (0원부터 시작)
let savingHistory = []; // 저금 기록을 저장할 배열 만들기
console.log(name + "님의 저금통이 준비되었습니다!");
// 저금통 조작 함수 (클로저!)
function piggyBank(action, amount) {
if (action === "넣기") { // 돈 넣기 작업인지 확인하기
totalMoney = totalMoney + amount; // 총 저금에 돈을 더하기
savingHistory.push(amount + "원 저금 (+)"); // 저금 기록에 추가하기
console.log(name + "님이 " + amount + "원을 저금했어요! 총 " + totalMoney + "원"); // 저금 완료 메시지
} else if (action === "빼기") { // 돈 빼기 작업인지 확인하기
if (totalMoney >= amount) { // 저금된 돈이 충분한지 확인하기
totalMoney = totalMoney - amount; // 총 저금에서 돈을 빼기
savingHistory.push(amount + "원 사용 (-)"); // 사용 기록에 추가하기
console.log(name + "님이 " + amount + "원을 사용했어요! 남은 돈 " + totalMoney + "원"); // 사용 완료 메시지
} else {
console.log(name + "님, 돈이 부족해요! 현재 " + totalMoney + "원 있어요."); // 돈 부족 메시지
}
} else if (action === "확인") { // 잔액 확인 작업인지 확인하기
console.log(name + "님의 저금통에 " + totalMoney + "원이 있어요!"); // 현재 잔액 메시지
} else if (action === "기록") { // 기록 보기 작업인지 확인하기
console.log(name + "님의 저금 기록:"); // 기록 제목 출력하기
for (let i = 0; i < savingHistory.length; i++) { // 저금 기록을 하나씩 반복하기
console.log((i + 1) + ". " + savingHistory[i]); // 순번과 함께 각 기록 출력하기
}
}
return totalMoney; // 현재 총 저금액 반환하기
}
return piggyBank; // 클로저 함수 반환!
}
// 여러 개의 독립적인 저금통 만들기
console.log("=== 나만의 저금통 실험 ===");
let alicePiggy = createPiggyBank("앨리스"); // 앨리스 저금통 만들기
let bobPiggy = createPiggyBank("밥"); // 밥 저금통 만들기
console.log("\n--- 앨리스 저금통 사용 ---");
alicePiggy("넣기", 1000); // 1000원 저금하기
alicePiggy("넣기", 500); // 500원 더 저금하기
alicePiggy("빼기", 300); // 300원 사용하기
console.log("\n--- 밥 저금통 사용 (완전히 독립적!) ---");
bobPiggy("넣기", 2000); // 2000원 저금하기
bobPiggy("빼기", 100); // 100원 사용하기
console.log("\n--- 각 저금통의 기록 확인 ---");
alicePiggy("기록"); // 앨리스 저금통 기록 보여주기
bobPiggy("기록"); // 밥 저금통 기록 보여주기
console.log("\n🎯 각 저금통이 자신만의 상태를 독립적으로 기억해요!");
이 예제는 정보 보호의 개념을 보여줍니다. 각 저금통의 내부 상태(잔액, 기록)는 밖에서 직접 접근할 수 없고, 오직 제공된 함수를 통해서만 안전하게 사용할 수 있습니다.
🔄 클로저 사용하는 순서 정리하기
지금까지 배운 클로저의 동작 과정을 자연스럽게 정리해보겠습니다.
첫 번째 단계는 바깥쪽 함수 설계입니다. 기억하고 싶은 정보와 환경을 담을 바깥쪽 함수를 만듭니다. 이는 마치 개인 서랍장을 준비하는 과정과 같습니다.
두 번째 단계는 환경 정보 준비입니다. 바깥쪽 함수 안에 클로저가 기억할 변수들을 만듭니다. 이는 서랍장에 소중한 물건들을 넣어두는 과정입니다.
세 번째 단계는 안쪽 함수 생성입니다. 바깥쪽 변수들을 사용하는 안쪽 함수를 만듭니다. 이 함수가 나중에 클로저가 될 것입니다.
네 번째 단계는 클로저 반환입니다. 바깥쪽 함수에서 안쪽 함수를 return으로 반환합니다. 이때 비로소 클로저가 탄생합니다.
다섯 번째 단계는 환경 보존입니다. 바깥쪽 함수가 끝나더라도 안쪽 함수는 바깥쪽 환경을 계속 기억하고 보존합니다.
마지막으로 가장 중요한 것은 클로저 활용입니다. 언제든지 클로저 함수를 불러서 보존된 환경의 정보를 사용할 수 있습니다.
🧚♀️ 이야기로 다시 배우기: 우리 회사의 직원 카드
지금까지 배운 내용을 하나의 조용한 이야기로 다시 정리해보겠습니다.
작은 회사에 입사한 여러분을 환영합니다. 이 회사에는 참 따뜻한 시스템이 있습니다.
입사 첫날, 인사팀 담당자가 각 직원에게 개인 전용 카드를 만들어줍니다. 이 카드는 참 똑똑합니다.
담당자는 먼저 개인 정보 시스템을 하나 만듭니다. 그 안에는 직원의 이름, 부서, 입사일, 접근 권한 등 그 직원만의 정보들이 들어있습니다. 그다음 스마트 카드를 만들어서, 이 카드에 개인 정보 시스템의 모든 정보에 접근할 수 있는 특별한 능력을 부여합니다.
이제 담당자는 다른 업무를 보러 가지만, 놀랍게도 카드는 여전히 그 직원만의 정보 시스템을 완벽하게 기억하고 있습니다.
더 흥미로운 것은, 철수의 카드는 철수의 정보만 알고 있고, 영희의 카드는 영희의 정보만 알고 있다는 점입니다. 서로 섞이지 않습니다.
그래서 철수가 카드를 찍으면 "철수님, 개발팀 출입을 허용합니다!"라고 정확히 인식해주고, 영희의 카드는 영희만의 권한을 확인해줍니다.
이 스마트 카드가 바로 클로저이고, 인사팀 담당자가 바깥쪽 함수, 개인 정보 시스템이 각 함수의 고유한 환경입니다. 담당자가 다른 일을 하러 간 후(함수가 끝난 후)에도 카드는 여전히 모든 것을 기억하고 있습니다.
🧠 자주 하는 실수와 주의할 점
클로저를 배우면서 많은 분들이 실수하는 부분들을 미리 알아두면 더 안전한 코딩을 할 수 있습니다.
❌ 실수 1: 클로저가 생성되지 않는 경우
// 클로저가 안 되는 경우
function notClosure() {
let message = "메시지";
function inner() {
let innerMessage = "내부 메시지"; // 바깥쪽 변수를 사용하지 않음
return innerMessage;
}
return inner(); // 함수 실행 결과를 반환 (함수 자체가 아님!)
}
// 올바른 클로저 만들기
function realClosure() {
let message = "메시지";
function inner() {
return message; // 바깥쪽 변수 사용! (클로저 조건 충족)
}
return inner; // 함수 자체를 반환! (클로저 생성)
}
let result1 = notClosure(); // "내부 메시지" (일반 함수 결과)
let result2 = realClosure(); // 클로저 함수
console.log(result2()); // "메시지" (클로저 동작!)
이런 실수가 생기는 이유는 함수를 반환하는 것과 함수 실행 결과를 반환하는 것을 헷갈리기 때문입니다. 클로저를 만들려면 반드시 함수 자체를 반환해야 합니다.
❌ 실수 2: 반복문에서 클로저 문제
// 문제가 있는 코드
function createProblematicFunctions() {
let functions = [];
for (var i = 0; i < 3; i++) {
functions.push(function() {
return i; // 모든 함수가 같은 i를 참조!
});
}
return functions;
}
let problemFuncs = createProblematicFunctions();
console.log("문제 상황:");
console.log(problemFuncs[0]()); // 3 (예상: 0)
console.log(problemFuncs[1]()); // 3 (예상: 1)
console.log(problemFuncs[2]()); // 3 (예상: 2)
// 해결된 코드
function createCorrectFunctions() {
let functions = [];
for (let i = 0; i < 3; i++) { // var 대신 let 사용!
functions.push(function() {
return i; // 각자 다른 i를 참조
});
}
return functions;
}
let correctFuncs = createCorrectFunctions();
console.log("해결된 상황:");
console.log(correctFuncs[0]()); // 0
console.log(correctFuncs[1]()); // 1
console.log(correctFuncs[2]()); // 2
이 문제는 var의 특성 때문에 생깁니다. let을 사용하면 각 반복마다 새로운 변수가 만들어져서 문제가 해결됩니다.
❌ 실수 3: 클로저와 일반 함수 헷갈리기
// 클로저처럼 보이지만 아닌 경우
function notRealClosure() {
function inner() {
let localVar = "지역 변수"; // 바깥쪽 변수를 사용하지 않음
return localVar;
}
return inner;
}
// 진짜 클로저
function realClosure() {
let outerVar = "바깥쪽 변수"; // 바깥쪽 변수 선언
function inner() {
return outerVar; // 바깥쪽 변수 사용! (클로저!)
}
return inner;
}
let func1 = notRealClosure();
let func2 = realClosure();
console.log("가짜 클로저:", func1()); // "지역 변수"
console.log("진짜 클로저:", func2()); // "바깥쪽 변수"
진짜 클로저인지 확인하려면 안쪽 함수가 바깥쪽 함수의 변수를 사용하는지 확인해야 합니다.
✏️ 연습문제로 개념 다지기
이제 배운 내용을 연습문제를 통해 차근차근 익혀보겠습니다.
Ex1) 간단한 숫자 기억 함수를 클로저로 만들어보세요
function createNumberKeeper(number) {
return function() { // 숫자를 기억하는 안쪽 함수 만들기
console.log("기억하고 있는 숫자:", number); // 기억하고 있는 숫자 출력하기
return number; // 기억하고 있는 숫자 반환하기
};
}
let keepTen = createNumberKeeper(10); // 10을 기억하는 클로저 만들기
let keepTwenty = createNumberKeeper(20); // 20을 기억하는 클로저 만들기
keepTen(); // "기억하고 있는 숫자: 10" 출력하기
keepTwenty(); // "기억하고 있는 숫자: 20" 출력하기
Ex2) 카운터 클로저를 만들어서 숫자를 하나씩 증가시켜보세요
function createCounter() {
let count = 0; // 카운트를 0부터 시작하기
return function() { // 카운트를 증가시키는 안쪽 함수 만들기
count = count + 1; // 카운트를 1 증가시키기
console.log("현재 카운트:", count); // 현재 카운트 값 출력하기
return count; // 현재 카운트 값 반환하기
};
}
let counter = createCounter(); // 카운터 클로저 만들기
counter(); // 1 출력하기
counter(); // 2 출력하기
counter(); // 3 출력하기
Ex3) 다음 코드의 출력 결과를 예측해보세요
function outer() {
let x = 5; // 바깥쪽 변수 x를 5로 초기화하기
function inner() {
x = x + 1; // 바깥쪽 변수 x를 1 증가시키기
console.log(x); // 증가된 x 값 출력하기
}
return inner; // 안쪽 함수 반환하기 (클로저 생성)
}
let func = outer(); // 클로저 함수 받기
func(); // 첫 번째 실행: 6 출력하기
func(); // 두 번째 실행: 7 출력하기
정답: 첫 번째는 6, 두 번째는 7이 출력됩니다.
해설: 클로저는 변수를 복사하는 것이 아니라 참조하므로, 같은 변수 x의 값이 계속 변경됩니다.
🤔 심화 문제로 실력 확인하기
기본 연습을 마쳤다면, 이제 조금 더 깊이 있는 문제들을 통해 클로저에 대한 이해를 확인해보겠습니다.
Q1. 다음 코드에서 클로저가 어떻게 동작하는지 분석해보세요.
function createAdder(x) {
return function(y) {
return x + y;
};
}
let addFive = createAdder(5);
let addTen = createAdder(10);
console.log(addFive(3)); // 결과는?
console.log(addTen(3)); // 결과는?
정답:
addFive(3):8(5 + 3)addTen(3):13(10 + 3)
해설: 각 클로저는 서로 다른 x 값을 기억합니다. addFive는 5를, addTen은 10을 기억하여 독립적으로 동작합니다.
Q2. 클로저를 사용해서 안전한 비밀번호 관리자를 만들어보세요.
function createPasswordManager(masterPassword) {
let passwords = {}; // 비밀번호 저장소
return function(action, service, password) {
if (action === "login") { // 로그인 동작인지 확인하기
if (password === masterPassword) { // 마스터 비밀번호가 맞는지 확인하기
console.log("✅ 마스터 비밀번호 인증 성공!"); // 인증 성공 메시지 출력하기
return true; // 성공 신호 반환하기
} else {
console.log("❌ 마스터 비밀번호가 틀렸습니다!"); // 인증 실패 메시지 출력하기
return false; // 실패 신호 반환하기
}
} else if (action === "save") { // 저장 동작인지 확인하기
if (password === masterPassword) { // 마스터 비밀번호가 맞는지 확인하기
let newPassword = "example123"; // 새 비밀번호 (실제로는 입력받음)
passwords[service] = newPassword; // 서비스별 비밀번호 저장하기
console.log(service + "의 비밀번호가 저장되었습니다."); // 저장 완료 메시지 출력하기
return true; // 성공 신호 반환하기
} else {
console.log("❌ 마스터 비밀번호가 필요합니다!"); // 권한 없음 메시지 출력하기
return false; // 실패 신호 반환하기
}
} else if (action === "get") { // 조회 동작인지 확인하기
if (password === masterPassword) { // 마스터 비밀번호가 맞는지 확인하기
if (passwords[service]) { // 해당 서비스의 비밀번호가 있는지 확인하기
console.log(service + " 비밀번호: " + passwords[service]); // 비밀번호 출력하기
return passwords[service]; // 비밀번호 반환하기
} else {
console.log(service + "의 비밀번호가 없습니다."); // 비밀번호 없음 메시지 출력하기
return null; // 없음 신호 반환하기
}
} else {
console.log("❌ 마스터 비밀번호가 필요합니다!"); // 권한 없음 메시지 출력하기
return false; // 실패 신호 반환하기
}
}
};
}
// 사용 예시
let passwordManager = createPasswordManager("master123"); // 비밀번호 관리자 만들기
passwordManager("login", "", "master123"); // 로그인 테스트하기
passwordManager("save", "email", "master123"); // 이메일 비밀번호 저장하기
passwordManager("get", "email", "master123"); // 이메일 비밀번호 조회하기
정답: 클로저를 활용하여 마스터 비밀번호와 저장된 비밀번호들을 안전하게 보호하는 시스템을 만들 수 있습니다.
해설: 밖에서는 비밀번호 저장소에 직접 접근할 수 없고, 오직 올바른 마스터 비밀번호를 통해서만 접근할 수 있어 보안이 유지됩니다.
💫 마무리하며
지금까지 클로저라는 정말 신기하고 아름다운 개념에 대해 자세히 알아보았습니다. 마치 개인 서랍장이나 스마트 카드처럼, 함수가 자신만의 환경을 기억하고 보호하는 놀라운 능력을 배웠습니다.
가장 중요한 것은 클로저가 함수가 끝난 후에도 환경을 기억한다는 점과, 각각의 클로저가 독립적인 환경을 가진다는 점입니다. 이제 여러분도 클로저를 사용해서 더욱 안전하고 아름다운 프로그램을 만들 수 있을 것입니다.
다음 시간에는 더 흥미진진한 자바스크립트 개념들을 함께 탐험해보겠습니다. 여러분의 코딩 실력이 하루하루 성장하는 모습을 보니 정말 뿌듯합니다. 계속 함께 걸어가겠습니다. ✨
✅ 학습 완료 체크리스트
이번 시간에 배운 내용들을 모두 이해했는지 확인해보세요.
| 학습 내용 | 이해했나요? |
|---|---|
| 클로저의 기본 개념 | ✅ |
| 기본 사용법과 문법 | ✅ |
| 주요 특징과 차이점 | ✅ |
| 자주 하는 실수들 | ✅ |
| 실전 예제 이해 | ✅ |
| 5단원 함수 복습 | ✅ |
🎯 추가 연습 문제들
조금 더 연습하고 싶은 분들을 위한 추가 문제들입니다.
추가 문제 1. 간단한 카운터 클로저를 만들어보세요.
function createCounter() {
let count = 0;
return function() {
count = count + 1;
console.log("현재 카운트:", count);
return count;
};
}
let counter = createCounter();
counter(); // 1
counter(); // 2
counter(); // 3
추가 문제 2. 이름을 기억하는 인사 함수를 클로저로 만들어보세요.
function createGreeter(name) {
return function(greeting) {
return greeting + ", " + name + "!";
};
}
let greetAlice = createGreeter("앨리스");
console.log(greetAlice("안녕하세요")); // "안녕하세요, 앨리스!"
console.log(greetAlice("좋은 아침")); // "좋은 아침, 앨리스!"
🔄 단계별 진행 과정 정리
지금까지 배운 내용을 단계별로 다시 한번 정리해보겠습니다.
1단계 과정: 1) 외부 함수에서 비밀 데이터 생성 → 2) 내부 함수가 외부 데이터 참조 → 3) 내부 함수를 반환하여 클로저 생성 → 4) 외부 함수 종료되지만 데이터는 보존 → 5) 클로저 함수 실행 시 외부 데이터에 접근 가능
2단계 과정: 1) 함수 공방으로 여러 클로저 생성 → 2) 각 클로저는 독립적인 환경 보유 → 3) 같은 코드지만 다른 데이터를 기억 → 4) 각각 독립적으로 상태 변경 → 5) 서로 영향 주지 않음
3단계 과정: 1) 민감한 데이터를 외부 함수에 저장 → 2) 안전한 접근 방법만 제공하는 클로저 생성 → 3) 외부에서는 클로저 함수를 통해서만 데이터 접근 → 4) 직접적인 데이터 조작 차단 → 5) 완전한 데이터 보호 달성
📂 마무리 정보
오늘 배운 6.2.1 내용이 여러분의 자바스크립트 지식 보관함에 잘 저장되었나요? 다음 시간에는 더 흥미진진한 내용으로 만나겠습니다.
기억할 점: 오늘 배운 내용을 꼭 연습해보시고, 궁금한 점이 있으면 언제든 다시 돌아와서 읽어보세요.
무료 JavaScript 학습 플랫폼에서 단계별 학습과 실시간 코드 실행을 통해
더욱 효과적이고 재미있게 학습하실 수 있습니다.