-
Notes from reading 🔖 You Don't Know JS Yet - 13JavaScript 2026. 5. 25. 20:59
### 파트2 - Chapter 7 클로저 사용법 / 클로저 생명주기 & 가비지 컬랙션
- 마지막 함수 참조가 삭제되면, 변수에 대한 클로저가 삭제되고, 변수는 GC 처리됨
- 클로저는 변수의 GC를 예기치 않게 막아 메모리 사용량을 급증시킬 위험성이 있다 !!
- 필요하지 않은 함수 참조는 적절하게 제거해줘야 한다. (복잡한 내용. 예제를 잘 봐야 할듯)
- 클로저는 변수를 기준으로 작동하지 스코프를 기준으로 작동하지 않는다.
- 현대의 많은 JS 엔진들은 명시적으로 참조되지 않는 변수를 클로저 스코프에서 제거하는 최적화 방식을 채택함
- 다만, eval등을 사용했을 때 등 특정 상황에서는 해당 최적화가 진행될 수 X => 따라서 모든 변수가 클로저 스코프에 남아있을 수 있다.
- >>> 클로저 구현은 스코프 단위 O 함수가 정의된 스코프를 포함, 외부 스코프의 모든 변수에 접근할 수 있다.
- 현대 JS 엔진이 클로저를 최적화해서 실제 함수 내에서 참조되는 변수만 클로저에 포함시켰을 뿐. (필요하지 않은 변수를 제거해 메모리 최적화, 클로저를 포함하는 스코프의 크기를 최적화)
- 다만, 해당 최적화가 모든 환경에서 적용될거라고 생각하면 X 수동으로 값을 버리는 편이 안전. 클로저 최적화나 GC에 의존하는 것은 좋지 않다.
- =>> 클로저가 어디에 있고 어떤 변수를 포함하는지 파악하는 것은 중요.
- JS에서 함수는 다른 값들 처럼 전달될 수 있는 일급 객체 => 클로저는 함수가 어디로 이동하던 해당 함수를 외부 스코프에 있는 변수와 연결해주는 연결고리
- 클로저는 함수가 정의된 원래 스코프에 대한 링크를 제공 => 클로저는 외부 스코프의 변수에 접근 O
- JS는 함수에서 비원시값과 마찬가리조 참조에 의해 저장되고 참조 복사를 통해 할당 전달 되는 사실을 알자
- 해당 관점에 집중해서 생각하면, 함수 인스턴스에 대한 참조가 존재하면, 함수 인스턴스와 그 전체 스코프 환경 & 스코프 체인을 유지하게 함 => 클로저
- 클로저를 잘 사용하면, 프로그램 내에서 좀 더 적절하게 변수의 노출 범위를 조절할 수 있다. (전역으로 사용하는 대신)
- 클로저를 사용한 대표적인 함수형 프로그래밍 패러다임의 기법 => 부분 적용 & 커링
- 여러 입력이 필요한 함수의 모양을 바꿔 미리 혹은 나중에 입력하는 기법 (초기 입력을 클로저가 기억) => 내부 정보를 캡슐화 & 의미있는 네이밍 가능
- 일부 함수를 분리하여 로직을 공통화 할 수도 있음 (함수 정의의 재사용 => 전문화된 함수 생성)
/** * Chapter 7 - 클로저 사용법 / 클로저 생명주기 & 가비지 컬렉션 * * 실행 방법: * node closure-lifecycle.js * * 각 예제를 순서대로 실행하며 클로저의 특징을 보여준다. */ console.log("===== Example 1. 클로저 기본 개념 ====="); /** * 클로저는 함수가 선언된 당시의 외부 변수에 접근 가능하게 한다. */ function createCounter() { let count = 0; return function increase() { count++; console.log("count:", count); }; } const counter = createCounter(); counter(); // 1 counter(); // 2 counter(); // 3 console.log("\n===== Example 2. 함수 참조가 남아있으면 클로저도 살아있다 ====="); /** * inner 함수가 outer 함수의 변수(message)를 계속 참조한다. * 함수 참조가 살아있는 동안 message도 GC 대상이 되지 않는다. */ function outer() { const message = "나는 클로저에 의해 유지된다"; return function inner() { console.log(message); }; } let savedFunction = outer(); /** * outer 함수 실행은 끝났지만, * savedFunction이 살아있기 때문에 message도 유지된다. */ savedFunction(); /** * 마지막 함수 참조 제거 * 이제 inner 함수와 관련 스코프는 GC 대상이 될 수 있다. */ savedFunction = null; console.log("savedFunction 참조 제거 완료"); console.log("\n===== Example 3. 클로저로 인해 메모리가 유지될 수 있음 ====="); /** * 큰 데이터를 클로저가 참조하면 메모리 사용량이 유지될 수 있다. */ function createHeavyClosure() { const hugeArray = new Array(1_000_000).fill("🔥"); return function getFirstItem() { return hugeArray[0]; }; } let heavyFn = createHeavyClosure(); console.log("클로저가 hugeArray를 유지중:", heavyFn()); /** * 함수 참조 제거 * hugeArray도 GC 대상이 될 가능성이 생김 */ heavyFn = null; console.log("heavyFn 제거 완료"); console.log("\n===== Example 4. 클로저는 '변수'를 기억한다 ====="); /** * 클로저는 값(snapshot)이 아니라 변수 자체를 참조한다. */ function createUpdater() { let value = 1; return { increase() { value++; }, print() { console.log("value:", value); }, }; } const updater = createUpdater(); updater.print(); // 1 updater.increase(); updater.print(); // 2 updater.increase(); updater.print(); // 3 console.log("\n===== Example 5. 잘못된 클로저 사용 예시 (var) ====="); /** * var는 함수 스코프이므로 * 모든 함수가 같은 i 변수를 참조한다. */ function wrongLoopExample() { const functions = []; for (var i = 0; i < 3; i++) { functions.push(function () { console.log("wrong i:", i); }); } return functions; } const wrongFns = wrongLoopExample(); wrongFns[0](); // 3 wrongFns[1](); // 3 wrongFns[2](); // 3 console.log("\n===== Example 6. 올바른 클로저 사용 예시 (let) ====="); /** * let은 블록마다 새로운 변수 생성 */ function correctLoopExample() { const functions = []; for (let i = 0; i < 3; i++) { functions.push(function () { console.log("correct i:", i); }); } return functions; } const correctFns = correctLoopExample(); correctFns[0](); // 0 correctFns[1](); // 1 correctFns[2](); // 2 console.log("\n===== Example 7. 부분 적용 (Partial Application) ====="); /** * 일부 인자를 먼저 고정 * 나머지 인자를 나중에 받는다. */ function multiply(a, b) { return a * b; } function partialMultiply(a) { return function (b) { return multiply(a, b); }; } const double = partialMultiply(2); const triple = partialMultiply(3); console.log("double(10):", double(10)); // 20 console.log("triple(10):", triple(10)); // 30 console.log("\n===== Example 8. 커링 (Currying) ====="); /** * 여러 인자를 각각 나눠서 받는 방식 */ function curryAdd(a) { return function (b) { return function (c) { return a + b + c; }; }; } console.log("curryAdd(1)(2)(3):", curryAdd(1)(2)(3)); console.log("\n===== Example 9. 캡슐화 ====="); /** * 외부에서 직접 접근 불가능한 private 변수 */ function createBankAccount(initialBalance) { let balance = initialBalance; return { deposit(amount) { balance += amount; console.log(`${amount}원 입금`); }, withdraw(amount) { balance -= amount; console.log(`${amount}원 출금`); }, getBalance() { return balance; }, }; } const account = createBankAccount(1000); account.deposit(500); account.withdraw(200); console.log("현재 잔액:", account.getBalance()); /** * 직접 접근 불가능 */ console.log("account.balance:", account.balance); // undefined console.log("\n===== Example 10. 불필요한 참조 제거 ====="); /** * 이벤트 핸들러, 캐시, 타이머 등에서 * 클로저 참조를 오래 유지하면 메모리 누수 위험 존재 */ function setupListener() { const largeData = new Array(500_000).fill("DATA"); function listener() { console.log("listener 실행:", largeData[0]); } return listener; } let eventListener = setupListener(); /** * 어딘가에 등록되었다고 가정 */ eventListener(); /** * 사용 종료 후 참조 제거 */ eventListener = null; console.log("eventListener 제거 완료"); console.log("\n===== 모든 예제 종료 =====");'JavaScript' 카테고리의 다른 글
Axios 사태로 다시 생각해보는 “안전하게 npm 패키지 사용하는 방법” (0) 2026.06.01 Notes from reading 🔖 You Don't Know JS Yet - 12 (0) 2026.05.17 Notes from reading 🔖 You Don't Know JS Yet - 11 (0) 2026.05.09 함수 파라미터 기본값, 왜 undefined는 되고 null은 안 될까? (0) 2026.03.14 Notes from reading 🔖 You Don't Know JS Yet - 10 (1) 2026.01.17