본문으로 건너뛰기

Ch5. 클로저

클로저란?

클로저란 함수가 선언될 당시의 렉시컬 스코프를 기억하며, 함수가 그 스코프 밖에서 실행될 때에도 해당 스코프에 접근할 수 있게 하는 기능이다.


5.1 깨달음

function foo() {
var a = 2;

function bar() {
console.log(a);
}

bar();
}
foo();
  • bar()는 단순히 스코프 규칙에 따라 a를 참조
  • 클로저 일부이지만 명확히 드러나진 않음

5.2 클로저 예시

function foo() {
var a = 2;

function bar() {
console.log(a);
}

return bar;
}

var baz = foo();
baz(); // 2
  • bar()foo()의 스코프를 기억하고 있음
  • foo()의 실행이 끝난 후에도 bar()는 여전히 a에 접근 가능 → 클로저

5.3 클로저의 작동 방식

var fn;

function foo() {
var a = 2;
function baz() {
console.log(a);
}
fn = baz;
}

function bar() {
fn(); // 2
}

foo();
bar();
  • bazfoo의 렉시컬 스코프에 대한 클로저를 가지므로 여전히 a를 참조 가능

setTimeout 클로저

function wait(message) {
setTimeout(() => {
console.log(message);
}, 1000);
}
wait("Hello, Closure!");
  • setTimeout 콜백도 message에 접근 가능 → 클로저

jQuery 예제

function setupBot(name, selector) {
$(selector).click(function activator() {
console.log("Activating: " + name);
});
}
setupBot("closure bot 1", "#bot_1");
setupBot("closure bot 2", "#bot_2");
  • 이벤트 핸들러 내부에서도 setupBot의 변수 name을 참조함 → 클로저

IIFE 예제

function foo() {
var a = 2;
(function IIFE() {
console.log(a);
})();
}
  • 이 경우는 스코프 안에서 호출 → 명확한 클로저는 아님

5.4 반복문과 클로저

for (var i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log(i);
}, i * 1000);
}
// 출력: 6, 6, 6, 6, 6

해결책 1: 새 스코프 생성

for (var i = 1; i <= 5; i++) {
(function() {
var j = i;
setTimeout(function timer() {
console.log(j);
}, i * 1000);
})();
}

해결책 2: 인자 전달

for (var i = 1; i <= 5; i++) {
(function(j) {
setTimeout(function timer() {
console.log(j);
}, j * 1000);
})(i);
}

해결책 3: let 사용

for (let i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log(i);
}, i * 1000);
}

let은 반복 시마다 새로운 스코프를 생성함


5.5 모듈

모듈 패턴

function CoolModule() {
var something = "cool";
var another = [1, 2, 3];

function doSomething() {
console.log(something);
}

function doAnother() {
console.log(another.join(", "));
}

return {
doSomething,
doAnother,
};
}

var coolModule = CoolModule();
coolModule.doSomething();
coolModule.doAnother();
  • 클로저를 활용한 데이터 은닉, API 공개

싱글톤 모듈 (IIFE)

var foo = (function CoolModule() {
// ...
return {
doSomething,
doAnother,
};
})();

모듈 로더 예시

var MyModules = (function Manager() {
var modules = {};

function define(name, deps, impl) {
for (var i = 0; i < deps.length; i++) {
deps[i] = modules[deps[i]];
}
modules[name] = impl.apply(impl, deps);
}

function get(name) {
return modules[name];
}

return {
define,
get,
};
})();

5.5.2 ES6 모듈

  • 파일 단위 모듈
  • import, export 사용
  • 정적으로 분석 가능, 런타임 의존성 없음
// bar.js
export function hello(who) {
return "Let me introduce: " + who;
}
// foo.js
import { hello } from "./bar.js";
const hungry = "hippo";
export function awesome() {
console.log(hello(hungry).toUpperCase());
}

5.6 정리

  • 클로저는 함수가 선언된 렉시컬 스코프를 기억하는 기능
  • 반복문, 타이머, 이벤트 핸들러에서도 활발히 사용됨
  • 모듈은 클로저를 활용한 구조이며, ES6 이후에는 파일 기반 정적 모듈 시스템이 도입됨