클로저는 함수와 렉시컬 환경의 조합. 함수가 생성될 당시의 외부 변수를 기억. 생성 이후에도 계속 접근 가능.
이라고 하면 무슨말인지 모르겠지 ??ㅋㅋㅋㅋㅋ
자. 말그대로 closure다. closure의 사전적 의미는 폐쇄.
함수를 닫아주는 역할이다.
외부함수를 닫아주는 중첩함수를 클로저라고 한다.
이렇게하면 무슨말인지 더 모르겠지?
그러니까 차근차근 알아가보도록하자.
렉시컬 환경이 뭔지부터 알아야지. 그러려면 렉시컬 스코프가 뭔지부터 알아야한다. 그 후에 클로저가 뭔지 보면 아주 조^^금^^ 이해가 쉬워짐.
말 그대로 범위이다.
자바스크립트에서의 스코프 종류
| 구분 | 설명 | 스코프 | 변수 |
|---|---|---|---|
| 전역 | 코드의 가장 바깥 영역 | 전역 스코프 | 전역 변수 |
| 지역 | 함수 몸체 내부 | 지역 스코프 | 지역 변수 |
((대충 스코프 설명 이미지)) 이건아닌데 나중에 교체하겠음

렉시컬 스코프 = 정적 스코프
자바스크립트를 비롯한 대부분의 프로그래밍 언어는 렉시컬 스코프를 따른다.
따라서, 함수를 어디서 정의했는지에 따라 상위 스코프를 결정한다.
함수가 호출된 위치는 상위 스코프 결정에 어떠한 영향도 주지 않는다.
즉, 함수의 상위 스코프는 언제나 자신이 정의된 스코프이다.
함수가 정의된 환경(위치)과 호출되는 환경(위치)은 다를 수 있다. 따라서, 렉시컬 스코프가 가능하려면 함수는 자신이 호출되는 환경과는 상관없이 자신이 정의된 환경, 즉 상위 스코프(=함수 정의가 위치하는 스코프)를 기억해야 한다.
이를 위해서 함수는 자신의 내부 슬롯 [[Environment]] 에 자신이 정의된 환경, 즉 상위 스코프의 참조를 저장한다.
그러니까 !!
렉시컬 환경이란, 함수 정의가 위치한 상위 스코프의 렉시컬 스코프를 저장해둔것을 의미한다는 뜻이겠지이~
ㅋㅋ이렇게말해도 어려움
대충 쉽게설명하자면, 그 함수가 정의될때 위에서부터 쭉읽혀진 코드가 저장된 환경을 함수의 렉시컬 환경이라고 부르는거같음.. 그니까 그밑에껀 사실 해당사항이 없는겨, 그 위에까지 실행된 코드..에서 정의된 함수나 선언된 변수... 그거만 기억하는겨.. 그밑에껀... 아무래도 상관이없는겨....(아마)
외부 함수보다 중첩 함수가 더 오래 유지되는 경우 중첩 함수는 이미 생명 주기가 종료한 외부 함수의 변수를 참조할 수 있다. 이러한 중첩 함수를 클로저 라고 한다.
그러니까 풀어서 설명을 하자면 ~
1. 함수안의 함수를 중첩함수라고 부른다.
2. 그럼 이제부터 편의상 함수를 함수1, 중첩함수를 함수1-1이라고 불러볼게~
3. 함수 1을 실행시키면 그 함수는 한번 돌면서 함수1-1을 실행시키게되고 할일이 끝나버렷.
4. 그럼 함수1은 수명이 끝난겨.
5. 근데 아직 그 안의 함수1-1은 할일이 안끝난겨
6. 여기서 이미 수명이 함수1보다 함수 1-1이 더 긺 = > 클로저 조건이잖슴
7. 근데 함수 1-1은 함수1에있는 변수를 아직 갖다가 쓸수가 있는겨
8. 왜냐. 함수를 정의할때 렉시컬 환경이 같이 정의되기 때문임.
9. 그럼 함수 1-1이 실행이 됨으로써 최종적으로 이 함수1을 끝내줄수가 있는겨
10. 그로니까 이름이 closure 가 되는게아닐 까 하는 킹리적 갓심..
//예시
function foo(){
const x = 1;
const y = 2;
//클로저
//중첩함수 bar는 외부 함수보다 더 오래 유지되며 상위 스코프의 식별자를 참조한다.
function bar(){
debugger;
console.log(x)
}
return bar;
}
const bar = foo();
bar();
// 클로저 예시
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
//생성자 함수로 표현하면
const Counter = (function(){
let num = 0;
function Counter(){
}
Counter.prototype.increase = function(){
return ++num;
};
Counter.prototype.decrease = function(){
return num > 0 ? --num : 0;
};
return Counter;
}());
const counter = new Counter();
console.log(counter.increase()); //1
console.log(counter.increase()); //2
console.log(counter.decrease()); //1
console.log(counter.decrease()); //0
//보조함수를 사용
//함수를 인수로 전달받고 함수를 반환하는 고차함수
//이 함수는 카운트 상태를 유지하기 위한 자유변수 counter를 기억하는 클로저를 반환한다.
function makeCounter(aux){
//카운트 상태를 유지하기 위한 자유 변수
let counter = 0;
//클로저를 반환
return function(){
counter = aux(counter);
return counter;
};
}
//보조함수
function increase(n){
return ++n;
}
function decrease(n){
return --n;
}
//함수로 함수를 생성한다.
//makeCounter 함수는 보조 함수를 인수로 전달받아 함수를 반환한다.
const increaser = makeCounter(increase);
console.log(increaser()); //1
console.log(increaser()); //2
//increaser 함수와는 별개로 독립된 렉시컬 환경을 갖기 때문에 카운터 상태가 연동하지 않는다.
const decreaser = makeCounter(decrease);
console.log(decreaser()); //-1
console.log(decreaser()); //-2
// 캡슐화와 은닉
function Person(name,age){
this.name = name; //public
let _age = age; //private
//인스턴스 메서드
this.sayHi = function(){
console.log(`Hi! My name is ${this.name}. I am ${_age}`)
};
}
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',20);
you.sayHi(); // Hi! My name is Kim. I am 30.
console.log(you.name); // Kim
console.log(you._age) // undefined
sayHi 메서드는 인스턴스 메서드이므로 Person 객체가 생성될 때마다 중복 생성된다.
sayHi 메서드를 프로토타입 메서드로 변경하여 sayHi 메서드의 중복 생성을 방지해보자
function Person(name,age){
this.name = name; //public
let _age = age; //private
//프로토타입 메서드
Person.prototype.sayHi = function(){
// Person 생성자 함수의 지역 변수 _age를 참조할 수 없다.
console.log(`Hi! My name is ${this.name}. I am ${_age}`);
}
}
그렇다면 이렇게 쓰면 됨
const Person = (function(){
let _age = 0; //private
// 생성자 함수
function Person(name, age){
this.name = name; //public
_age = age;
}
//프로토타입 메서드
Person.prototype.sayHi = function(){
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