JS 2.5 · 2부. 함수와 스코프

클로저 맛보기

함수가 만들어질 때의 바깥 변수를 계속 참조하는 것을 봅니다.

예상 학습 시간 15분 실행 단추가 있는 예제는 고쳐서 실행해 볼 수 있습니다 난이도 중급
개념 설명

함수가 바깥 변수를 계속 참조합니다

앞 단원에서 함수 안에서 만든 것은 함수가 끝나면 사라진다고 했습니다. 그런데 함수가 함수를 돌려주면 이야기가 달라집니다.

돌려받은 함수가 바깥 변수를 아직 참조하고 있으면, 바깥 함수가 끝났어도 그 변수는 남습니다. 이것을 클로저(closure) 라고 합니다.

최소 예제 · 편집 가능

카운터 만들기

script.js
function makeCounter() {
	let count = 0;

	return function () {
		count++;
		return count;
	};
}

const counter = makeCounter();

console.log(counter());
console.log(counter());
console.log(counter());
출력
1 2 3

makeCounter() 는 이미 끝났는데 count 가 남아 값이 쌓입니다. 돌려준 함수가 그것을 아직 참조하고 있기 때문입니다.

상세 사용법

부를 때마다 따로 생깁니다

makeCounter() 를 두 번 부르면 count 도 둘이 생깁니다. 서로 영향을 주지 않습니다.

script.js
function makeCounter() {
	let count = 0;
	return () => ++count;
}

const a = makeCounter();
const b = makeCounter();

console.log(a(), a(), a());
console.log(b());
출력
1 2 3 1

++count 는 1 늘린 뒤의 값을 냅니다. count++ 는 늘리기 전의 값을 내므로 여기서는 맞지 않습니다.

상세 사용법

밖에서 손댈 수 없습니다

count 는 함수 안에 있어 밖에서 읽거나 바꿀 수 없습니다. 정해 둔 방법으로만 다루게 만들 때 이 성질을 사용합니다.

script.js
function makeAccount(start) {
	let balance = start;

	return {
		deposit: amount => balance += amount,
		check: () => balance
	};
}

const account = makeAccount(1000);

account.deposit(500);
console.log(account.check());
console.log(account.balance);
출력
1500 undefined

마지막 줄이 undefined 입니다. balance 는 밖에 없습니다. 중괄호로 여러 함수를 묶어 돌려주는 방법은 3.6 에서 다시 다룹니다.

상세 사용법

정해 둔 값을 기억하는 함수

자주 사용하는 모양입니다. 값 하나를 미리 정해 둔 함수를 만들어 냅니다.

script.js
function makeMultiplier(times) {
	return n => n * times;
}

const double = makeMultiplier(2);
const triple = makeMultiplier(3);

console.log(double(10));
console.log(triple(10));
출력
20 30
흔한 실수

처음에 자주 걸리는 것

1. 돌려주면서 괄호를 붙입니다

return counter() 는 함수를 실행한 결과를 돌려줍니다. 함수 자체를 돌려주려면 괄호 없이 return counter 입니다(2.1).

2. 만드는 함수를 한 번만 부릅니다

makeCounter()() 처럼 매번 새로 만들면 값이 쌓이지 않습니다. 만든 함수를 변수에 담아 두고 그것을 여러 번 불러야 합니다.

console.log(makeCounter()());   // 늘 1 입니다
3. 값이 사라졌다고 생각합니다

바깥 함수가 끝나도 참조하는 함수가 살아 있으면 값은 남습니다. 반대로 그 함수를 더 이상 아무도 참조하지 않으면 값도 함께 정리됩니다.

실습 문제

직접 해보기

1. 거꾸로 세는 카운터 난이도 하

시작 값을 받아 부를 때마다 1씩 줄어드는 함수를 돌려주는 makeCountdown 을 만드세요.

function makeCountdown(start) { let current = start; return () => --current; } const down = makeCountdown(3); console.log(down(), down(), down()); // 2 1 0
2. 한 번만 실행되는 함수 난이도 중

처음 부를 때만 "실행했습니다" 를 돌려주고, 그 뒤로는 "이미 실행했습니다" 를 돌려주는 함수를 만드세요.

실행했는지를 담아 둘 변수 하나를 바깥에 두고, 안에서 그 값을 바꾸면 됩니다.
function makeOnce() { let done = false; return () => { if (done) { return "이미 실행했습니다"; } done = true; return "실행했습니다"; }; } const once = makeOnce(); console.log(once()); console.log(once());
요약

이 단원의 정리

  • 함수가 함수를 돌려주면, 돌려받은 함수가 바깥 변수를 계속 참조합니다.
  • 바깥 함수가 끝나도 그 변수는 남습니다.
  • 만드는 함수를 여러 번 부르면 변수도 그만큼 따로 생깁니다.
  • 그 변수는 밖에서 읽거나 바꿀 수 없습니다.
  • 값을 미리 정해 둔 함수를 만들어 내는 데 자주 사용합니다.