클로저가 뭐야?
함수와 함수가 선언된 어휘적 환경의 조합입니다.
함수 밖에서 선언된 변수를 함수 내부에서 사용할 때 클로저가 생성됩니다.
function outer() {
const name = 'leehyowon';
function inner() {
console.log(name);
}
return inner;
}
const innerFunc = outer();
innerFunc();
이제 var innerFunc = outer()를 통해서 inner 함수를 반환받게 되는데, 일반적으로 함수가 종료되면 메모리에서 소멸하기 때문에 아래와 같은 모습을 하게됩니다.
때문에 일반적으로, innerFunc를 호출해도 name에 대한 참조는 메모리에서 소멸하여 호출이 불가하게 됩니다.
하지만 클로저는 이러한(외부 변수와 같은) 환경 자체를 통째로 기억하는 공간을 형성합니다.
이러한 이유로 innerFunc는 여전히 자신의 외부에서 생성된 name이라는 변수를 참조할 수 있게 됩니다.
클로저의 활용
캡슐화
클로저 영역이 별도로 생성되는 모습은 마치 객체를 생성한 모습과 유사합니다.
자바스크립트의 var는 전역적인 특성을 지니고 있지만 클로저의 특성을 이용하면 private 변수를 갖는 클래스 형태로 만들 수 있습니다.
function student(name, score) {
// var name = name, score = score;
return {
setScore: function(_score) {
score = _score;
},
getInfo: function() {
return { name: name, score: score };
}
}
}
var lee = student("sunsin", 80);
var kim = student("yusin", 75);
lee.setScore(60);
console.log(lee.getInfo()); // { name: 'sunsin', score: 60 }
console.log(kim.getInfo()); // { name: 'yusin', score: 75 }
주의해야 할 점
var arr = [];
for (let i = 0; i < 5; i++) {
arr[i] = function() {
return i;
}
}
for (let j = 0; j < arr.length; j++) {
console.log(arr[j]());
}
위 코드는 외부 변수 i를 사용하는 익명함수를 arr 배열에 담는 일을 하고 있습니다.
하지만 콘솔창에는 5가 다섯번 출력됩니다.
이는 클로저가 생성되지만 전역변수 i가 소멸되지 않아 참조가 공유되기 때문이고, for문에 따른 모습은 아래와 같습니다.
위 처럼 5개의 클로저 모두 같은 전역변수 i를 참조하는 형태가 됩니다.
한가지 유의할점은 for문을 통해 i가 5까지 증가한다는 점 입니다.
만약 내부 함수에서 array[i]와 같은 참조가 일어나면 예상하지 못한 결과가 나타날 것 입니다.
이를 방지하기위해 익명함수를 한번 더 덮어서 i에 대한 참조를 독립시켜주는 클로저를 생성하거나 let 문법을 사용할 수 있습니다.
var arr = [];
for (let i = 0; i < 5; i++) {
arr[i] = function(i) {
return function() {
return i;
}
}(i);
}
for (let j = 0; j < arr.length; j++) {
console.log(arr[j]());
}
<클로저를 중첩으로 사용하여 전역변수 i의 참조영역 독립>
var arr = [];
for (let i = 0; i < 5; i++) {
arr[i] = function() {
return i;
}
}
for (let j = 0; j < arr.length; j++) {
console.log(arr[j]());
}
전역변수 i를 인자로 받는 즉시실행 함수에서 i는 익명함수의 파라미터로 동작하기 때문에 별도의 영역을 생성하며, 내부 함수는 이 별도의 영역의 i를 참조하기 때문에 지역변수를 사용한 것과 같은 효과를 냅니다.
let i for문 내 지역변수로 유효하기 때문에 사실상 각 i가 다른 변수처럼 취급됩니다.
위와 같은 방법으로 선언 시점의 환경을 독립시켜줄 수 있습니다.
이에 대한 참조 모습은 아래처럼 바뀌게 됩니다.
참고 : 반복문에서의 var 와 let의 속도 차이
https://gomugom.github.io/let-vs-var-performance-compare/