클로저란
상위 스코프의 식별자를 참조
하며,외부 함수보다 더 오래 유지 되는 중첩 함수
를 말한다. 자바스크립트는 렉시컬 스코프를 따르기 때문에 함수가 어디서 정의됐는지에 따라 상위 스코프를 결정한다. 함수 정의가 평가되어 함수 객체를 생성하는 시점인실행 중인 실행 컨텍스트의 렉시컬 환경
을 상위 스코프로 참조한다. 이에 함수는 함수 객체의 내부 슬롯인[[Environment]]
에 상위 스코프 참조를 저장한다. 따라서 함수는 언제나상위 스코프의 식별자를 참조
할 수 있으며,식별자에 바인딩된 값을 변경
할 수 있다.클로저를 반환하는 외부 함수는 콜스택에서 제거되지만 클로저의
[[Environment]]
에서 참조하고 있기 때문에 렉시컬 환경은 유지된다.
또한 클로저를 활용하면 캡슐화와 정보은닉 효과를 얻을 수 있다.
클로저란 함수와
그 함수가 선언된 렉시컬 환경
과의 조합
중첩함수 innerFunc의 상위 스코프 : outerFunc
innerFunc에서 outerFunc의 변수 x에 접근 가능 → 따라서 x값은 10
const x = 1;
function outerFunc() {
const x = 10;
function innerFunc() {
console.log(x); // 10
}
innerFunc();
}
innerFunc() 이 outerFunc() 내부에서 정의되지 않는다면 innerFunc() 은 outerFunc() 에 정의된 x 변수에 접근할 수 없다.
const x = 1;
function outerFunc() {
const x = 10;
innerFunc();
}
function innerFunc() {
consol.log(x); // 1
}
렉시컬 스코프를 따르기 위해선 자신이 정의된 환경인 상위 스코프를 기억
해야함
함수 정의가 평가되어 함수 객체를 생성할 때, 함수 객체의 내부 슬롯인 [[Environment]]
에 상위 스코프의 참조 저장
함수 정의가 평가되어 함수 객체를 생성하는 시점인 실행 중인 실행 컨텍스트의 렉시컬 환경
참조
foo 함수와 bar 함수가 전역에서 함수 선언문으로 정의
전역 코드가 평가되는 시점에 함수 객체를 생성하고 window의 프로퍼티로 생성
함수 정의가 평가되는 시점인 전역 코드를 평가할 때, [[Environment]]
슬롯에 전역 렉시컬 환경 참조값 저장
foo 함수가 호출될 때 함수 코드 평가 → 외부 렉시컬 환경에 대한 참조값을 [[Environment]]
에서 가져옴
const x = 1;
function foo() {
const x = 10;
bar();
}
function bar() {
console.log(x);
}
foo();
bar();
💡 중첩 함수가
상위 스코프의 식별자를 참조
하며,중첩 함수가 외부 함수보다 더 오래 유지
되는 중첩 함수
상위 스코프의 식별자 중 클로저가 참조하고 있는 식별자 → 자유변수
클로저는 자유 변수만 기억
클로저(closure)란 함수가 “자유 변수에 대해 닫혀있음” 또는 “자유 변수에 묶여 있는 함수”
함수는 언제나 자신이 기억하는 상위 스코프의 식별자를 참조
할 수 있으며, 식별자에 바인딩된 값을 변경 가능
outer 함수를 호출하면 중첩함수 inner를 반환하고 생명주기 마감
inner 함수는 자신이 평가될 때 결정된 상위 스코프를 [[Environment]]
내부 슬롯에 저장
저장된 상위 스코프는 inner함수가 존재하는 한 유지
const x = 1;
function outer() {
const x = 10;
const inner = function() { console.log(x); };
return inner;
}
const innerFunc = outer();
innerFunc(); // 10
outer 함수는 inner 함수를 반환하면서 생명주기 종료 → outer 함수의 실행 컨텍스트 제거
outer 함수의 실행 컨텍스트는 콜스택에서 제거되지만 렉시컬 환경은 유지
outer 함수의 렉시컬 환경 → inner 함수의 [[Environment]]
내부 슬롯에 의해 참조
inner 함수의 렉시컬 환경 → innerFunc에 의해 참조
- 중첩 함수가 외부 함수보다 더 오래 유지되지만 상위 스코프의 어떤 식별자도 참조하지 않는 경우
- 이때, 대부분의 모던 브라우저는 최적화를 통해 상위 스코프 기억 ❌
- 중첩 함수가 상위 스코프의 식별자를 참조하지만 중첩 함수를 반환하지 않는 경우
- 외부 함수보다 중첩 함수의 생명주기가 짧음
올바르게 동작하려면 다음 두 조건을 만족해야 함
1. num은 increase 함수가 호출되기 전까지 변경되지 않고 유지되어야 함
2. increase 함수만이 num을 변경할 수 있어야 함
const increase = (function() {
let num = 0;
return function() {
return ++num;
};
}());
console.log(increase()); // 1
console.log(increase()); // 2
console.log(increase()); // 3
이전 상태를 유지하며 increase 함수만이 num을 변경할 수 있도록 하기
즉시 실행함수
를 이용하여 num을 직접 참조할 수 없고, 함수는 호출된 이후 바로 소멸
시킴
클로저인 increase만 num의 상태를 변경 가능
const increase = (function() {
let num = 0;
return function() {
return ++num;
};
}());
console.log(increase()); // 1
console.log(increase()); // 2
const counter = (function() {
let num = 0;
return {
increase() {
return ++num;
},
decrease() {
return num > 0 ? --num : 0;
}
};
}());
console.log(counter.increase()); // 1
console.log(counter.increase()); // 2
console.log(counter.decrease()); // 1
console.log(counter.decrease()); // 0
인수로 전달되는 보조 함수(aux)에 상태 변경 위임
makeCounter가 반환하는 함수는 makeCounter 함수를 상위 스코프로 가지는 클로저
makeCounter 함수를 호출해 반환되는 함수는 자신만의 독립된 렉시컬 환경을 가짐
→ 호출할 때마다 새로운 makeCounter 함수 실행 컨텍스트 생성
function makeCounter(aux) {
let counter = 0;
return function() {
counter = aux(counter);
return counter;
};
}
function increase(n) {
return ++n;
}
function decrease(n) {
return --n;
}
const increaser = makeCounter(increase);
console.log(increaser()); // 1
const decreaser = makeCounter(decrease);
console.log(decreaser()); // -1
counter를 공유하려면 렉시컬 환경을 공유하는 클로저를 만들어야 함
makeCounter를 여러번 호출하면 안됨
const counter = (function(){
let counter = 0;
return function (aux) {
counter = aux(counter);
return counter;
};
}());
...
console.log(counter(increase)); // 1
console.log(counter(decrease)); // 0
자바스크립트는 private, public 등 접근 제한자가 존재하지 않는다.(_
: protected, #
: private 으로 최근에 생기긴 함)
객체의 모든 프로퍼티와 메서드는 기본적으로 public이다.
따라서 아래 코드처럼 클로저를 통해 정보를 private하게 만들 수 있다
const Person = (function() {
let _age = 0;
function Person(name, age) {
this.name = name;
_age = age;
}
Person.prototype.sayHi = function() {
console.log(`${this.name}, I'm ${_age}`);
};
return Person;
}());
const me = new Person('Lee', 20);
me.sayHi(); // Lee, I'm 20
console.log(me.name); // Lee
console.log(me._age); // undefined
생성자 함수 Person 을 반환하면서 _age는 접근할 수 없다.
하지만 인스턴스를 여러 개 생성하는 경우 프로토타입 메서드의 상위 스코프는 즉시실행함수
를 가리키므로 같은 _age 변수를 가리킨다.
따라서 인스턴스를 생성할 때 동일한 _age의 값이 변한다.
const me = new Person('Lee', 20);
const you = new Person('Kim', 30);
me.sayHi(); // Lee, I'm 30
이와 같이 자바스크립트는 정보 은닉을 완전하게 지원하지 않는다.