이번 포스팅은 https://dev.to/shimphillip/javascript-closure-simply-explained-1f79 포스팅을 참고하여 작성되었습니다.
이번에는 어렵다고 소문이 난 클로저(Closure)에 대해서 한 번 알아보겠다.
클로저(closure)는 스코프를 계속 들고 있는 것이다. 본래 함수 내부에 선언한 변수는 함수가 끝나면 사라지지만, 클로저가 스코프를 계속 들고 있으므로 그 함수 내부의 변수를 참조할 수 있게 된다. (때문에 메모리 문제의 주범이기도 하다)
한 가지 예를 들어보겠다.
아래의 코드를 실행하면 greeting 변수는 함수가 끝나 사라졌지만 hello 변수를 통해 greeting 변수의 값이 출력되는 것을 볼 수 있다.
function sayHello() {
const greeting = "Hello World"
return function() {
console.log(greeting);
}
}
const hello = sayHello(); // return 받은 함수를 저장한다.
hello(); // "Hello World" 가 출력된다.
즉, 클로저는 다른 함수에 의해 반환되는 상태 저장 함수이고, 부모 함수가 실행을 마친 경우에도 부모 범위의 변수와 매개 변수를 기억하는 컨테이너 역할을 한다고 생각을 하면 이해하기 쉬울 것이다.
내부에 선언한 변수를 기억하는 클로저(Closure)는 어디에 쓰일까?
1. 변수를 비공개로 유지할 수 있다.
아래의 코드에서 count 변수에 접근하려하면 count 변수가 전역환경에 노출되어 있지 않기 때문에 참조 오류가 발생한다.
function counter() {
let count = 0;
return function() {
count += 1;
return count;
}
}
const increment = counter();
console.log(increment()); // 1
console.log(increment()); // 2
console.log(count) // Reference error: count is not defined
2. 재사용 가능한 상태
function counter() {
let count = 0;
return function() {
count += 1;
return count;
}
}
const incrementBananaCount = counter();
const incrementAppleCount = counter();
console.log(incrementBananaCount()); // 1
console.log(incrementBananaCount()); // 2
console.log(incrementAppleCount()); // 1
3. 모듈 설계 패턴
getName과 changeName만을 반환하기 때문에 name변수를 직접 변경할 수 없다. 오로지 할 수 있는건 getName을 통한 name변수에 접근, changeName을 통한 name변수의 값 변경이다.
let Dog1 = (function() {
let name = "Suzy";
const getName = () => {
return name;
}
const changeName = (newName) => {
name = newName;
}
return {
getName: getName,
changeName: changeName
}
}())
console.log(name); // undefined
Dog1.getName() // Suzy
Dog1.changeName("Pink")
Dog1.getName() // Pink
아래의 코드를 실행한 결과는 무엇일까 ?
for(var i=0; i<5; i++) {
setTimeout(function() {
console.log(i)
}, 1000)
}
정답은 1초 후에 5가 5번 찍힌다
5
5
5
5
5
우리는 0, 1, 2, 3, 4 순서대로 찍히기를 원한다. 클로저의 특성을 활용하여 해결해 보자.
for (var i = 0; i < 5; i++) {
return setTimeout((function(index) {
return function() {
console.log(index);
}
}(i)), 1000);
}
}
커링 기법을 사용해 i를 인자로 넘겨주고 index라는 변수로 받아서 사용하고 있다. 또한 클로저로 선언된 익명함수에선 그 함수가 선언된 당시의 실행환경을 기억하고 있기 때문에 각 시점의 index 값을 기억하게 되고, 그로 인해 우리가 원하면 0, 1, 2, 3, 4 순서대로 콘솔이 찍히게 되는 것이다.
마지막으로 클로저가 아닌 또 다른 형태로 해결할 수 있다.
for(let i=0; i<5; i++) {
setTimeout((function() {
console.log(i)
}), 1000)
}
let 키워드는 블록 범위의 바인딩이 되기 때문에 우리가 원하는 출력을 얻을 수 있다.