클로저는 함수와 그 함수가 선언된 렉시컬 환경과의 조합이다.
const x = 1;
function foo() {
const x = 10;
bar();
}
function bar() {
console.log(x);
}
foo(); // 1
bar(); // 1
함수가 정의된 환경과 호출되는 환경은 다를 수 있다.
그런데 항상 자신이 정의된 환경을 기억한다는 것은, 함수 어딘가에 이것이 기록되어 있다는 의미이다.
함수는 자신의 내부 슬롯 [[Environment]]에 자신이 정의된 환경, 즉 상위 스코프의 참조를 저장한다.
다시 한 번 함수 코드 평가 순서를 살펴보면,
const x = 1;
function outer() {
const x = 10;
const inner = function () {
console.log(x);
};
return inner;
}
const innerFunc = outer(); // outer()의 실행 종료와 함께, 지역 변수 x와 inner는 소멸했을 것 같지만,
innerFunc(); // 10
잊지 말 것! 실행 컨텍스트와 렉시컬 환경은 별개의 객체로 관리된다. 함수 종료와 함께 실행 컨텍스트는 스택에서 소멸하지만, 렉시컬 환경은 가비지 컬렉션에 의해 계속 관리된다.
클로저는 상태를 안전하게 변경하고 유지하기 위해 사용한다.
=> 상태를 안전하게 은닉하고 특정 함수에게만 상태 변경을 허용하기 위해 사용한다.
예시 : 함수 호출 횟수를 누적해 출력하는 카운터를 보고 이해해보자.
let num = 0;
const increase = function () {
return ++num;
}
console.log(increase()); // 1
console.log(increase()); // 2
console.log(increase()); // 3
const increase = function () {
let num = 0;
return ++num;
};
console.log(increase()); // 1
console.log(increase()); // 1
console.log(increase()); // 1
const increase = (function() {
let num = 0;
return function() { // 클로저를 반환
return ++num;
};
}()); // 즉시 실행 함수 자체는 여기서 실행이 끝났다. increase에는 중첩함수가 담겼다.
console.log(increase()); // 1
console.log(increase()); // 2
console.log(increase()); // 3
...그런데 😥
const Person = (function() {
let _age = 0; // private
// 생성자 함수
function Person(name, age) {
this.name = name; // public
_age = age;
}
// 프로토타입 메서드
Person.prototype.sayHi = function() { // sayHi 메서드의 중복 생성을 막기 위해 prototype 메서드로 선언
console.log(`Hi! My name is ${this.name}. I am ${_age}.`);
};
// 생성자 함수를 반환
return Person;
}());
const me = new Person('Lee', 20);
me.sayHi(); // Hi! My name is Lee. I am 20.
console.log(me.name); // Lee
console.log(me._age); // undefined
const you = new Person('Kim', 30);
you.sayHi(); // Hi! My name is Kim. I am 30.
console.log(you.name); // Kim
console.log(you._age); // undefined
// 그런데...
// _age 변수 값이 변경된다!
me.sayHi(); // Hi! My name is Lee. I am 30.
var funcs = [];
for (var i = 0; i < 3; i++) {
funcs[i] = function() {
return i;
};
}
for (var j = 0; j < funcs.length; j++) {
console.log(funcs[j]()); // 기대 : 0, 1, 2 / 현실 : 3, 3, 3
}
var funcs = [];
for (var i = 0; i < 3; i++) {
funcs[i] = (function(id) {
return function() {
return id;
};
}(i));
}
for (var j = 0; j < funcs.length; j++) {
console.log(funcs[j]()); // 0, 1, 2
}
const funcs = [];
for (let i = 0; i < 3; i++) {
funcs[i] = function() { return i; }
}
for (let j = 0; j < funcs.length; j++) {
console.log(funcs[j]()); // 0, 1, 2
}