2023.02.18
자바스크립트 스터디 6회차
공부 사이트: https://poiemaweb.com/
18. 클로저
1) 클로저의 개념
클로저는 내부함수가 외부함수의 맥락(context)에 접근할 수 있는 매커니즘을 의미한다.
① 내부 함수를 외부 함수 내에서 호출하는 경우
function outer(){
var x = 10;
var inner = function() { console.log(x); };
inner();
}
outer(); //10
outer()의 내부함수 inner(). inner()는 자신을 포함하고 있는 outer()의 변수 x에 접근할 수 있다.
렉시컬 스코핑에 의해 inner()는 상위 스코프는 outer()이다.
#렉시컬 스코핑(Lexical scoping): 스코프는 함수를 호출할 때가 아니라 함수를 어디에 선언하였는지에 따라 결정된다. inner()의 선언위치는 outer()의 내부이기에 inner()의 상위 스코프는 outer()가 되는 것.
#실행 컨텍스트(Execution Context): 코드를 실행하기 위해 코드를 블록으로 나누는데, 코드 블록에 담겨있는 변수/함수/this/arguments .... 등에 대한 정보를 담고 있는 문맥
[실행 컨텍스트]
ⓐ내부함수 inner 호출
ⓑ실행 컨텍스트가 실행 컨텍스트 스택에 쌓임
ⓒ변수 객체, 스코프 체인, 바인딩할 객체 결정.
여기서 스코프 체인은 전역 스코프를 가리키는 전역 객체, outer()의 스코프를 가리키는 활성 객체 를 순차적 바인딩.
▷ 스코프 체인이 바인딩한 객체 = 렉시컬 스코프
[TIP] 실행 컨텍스트
- 전역 컨텍스트(Global Context): 자바스크립트가 코드를 실행할 때 생성.
- 함수 컨텍스트(Functional Context): 함수가 호출될 때마다 생성.
함수가 호출되면 새로운 실행 컨텍스트가 생성되어 실행 스택의 Top에 배치됨. 함수가 반환되면 스택에서 제거되고 이전 컨텍스트가 다시 시작.
▷ 내부 함수가 상위 스코프에 접근 가능한 것은 실행 컨텍스트의 스코프 체인을 자바스크립트 엔진이 검색했기 때문.
▷ 스코프 체인은 렉시컬 스코프의 참조를 순차적으로 저장하고 있다.
[스코프 체인 검색 과정]
ⓐ inner() 함수 스코프 내에서 변수 x 검색. → 실패
ⓑ inner() 함수를 포함하는 외부 함수 outer()의 스코프 내에서 변수 x 검색. → 성공
② 내부 함수를 외부 함수 내에서 반환하는 경우
function outer(){
var x = 10;
var inner = function() { console.log(x); };
return inner;
}
var inner = outer();
inner(); //10
외부 함수 outer()는 내부함수 inner()를 반환하고 종료된다.
함수가 반환되면 실행 스택에서 제거된다고 했으니, outer()의 실행 컨텍스트 또한 실행 스택에서 제거될 것이다.
그런데도 내부함수 inner()는 outer()의 변수 x에 접근할 수 있다. ✔
외부 함수보다 내부 함수가 오래 살아있는 경우,
외부 함수 밖에서 내부 함수가 호출됐을 때 내부 함수가 외부 함수의 지역변수에 접근할 수 있다. 이러한 함수를 클로저(Closure)라고 부른다.
③ 결론
✔ 클로저란?
- 반환된 내부함수가 자신이 선언됐을 때의 환경인 스코프를 기억
- 자신이 선언됐을 때의 환경 밖에서 호출되어도 그 환경에 접근할 수 있음.
▷ 자신이 생성될 때의 환경(Lexical environment)을 기억하는 함수
✔ 자유 변수
- 클로저에 의해 참조되는 외부함수의 변수
✔ 실행 컨텍스트 관점
- 내부 함수가 유효한 상태에서 외부 함수가 종료하여 외부 함수의 실행 컨텍스트가 반환되어도
- 외부 함수 실행 컨텍스트 내의 활성 객체는 내부 함수에 의해 참조되는 한 유효하다.
- 그리하여 내부 함수가 스코프 체인을 통해 참조할 수 있다.
※ 내부 함수는 외부 함수 내 변수의 복사본이 아니라 실제 변수에 접근한다. (참조 개념)
2) 클로저의 활용
① 상태 유지 ✔ 가장 유용!
<!DOCTYPE html>
<html>
<body>
<button class="toggle">toggle</button>
<div class="box" style="width: 100px; height: 100px; background: red;"></div>
<script>
var box = document.querySelector('.box');
var toggleBtn = document.querySelector('.toggle');
// 즉시 실행 함수 toggle
var toggle = (function () {
var isShow = false;
// ① 클로저를 반환
return function () {
box.style.display = isShow ? 'block' : 'none'; //display: none이면 감추기/block이면 보이기
// ③ 상태 변경
isShow = !isShow;
};
})();
// ② 이벤트 프로퍼티에 클로저를 할당
toggleBtn.onclick = toggle;
</script>
</body>
</html>
- 함수를 반환한 후 소멸하는 즉시 실행 함수.
- 렉시컬 환경의 변수인 isShow를 기억해야 하기 때문에, 즉시 실행 함수는 클로저를 반환한다.
- 이벤트 핸들러인 클로저를 제거하지 않는 한, 클로저가 기억하는 isShow는 소멸하지 않는다. (현재 상태 기억)
결론: 클로저는 현재 상태를 기억하고 이 상태가 변경되더라도 최신 상태를 유지해야 하는 경우 유용하다.
② 전역 변수의 사용 억제
전역 변수는 누구나 접근할 수 있고 변경할 수 있기 때문에 사용을 최소화 해야 한다. (버그 발생 가능)
전역 변수 대신 클로저를 사용하자.
전역 변수와 다르게 클로저가 기억하는 변수는 외부에서 직접 접근할 수 없는 private 변수이므로, 의도치 않은 변경을 방지할 수 있다.
③ 정보 은닉
function Counter(){
var counter = 0;
// 클로저
this.increase = function() {
return ++counter;
};
// 클로저
this.decrease = function() {
return --counter;
};
}
const c = new Counter();
console.log(c.increase()); //1
console.log(c.decrease()); //0
console.log(c.counter); //undefined
생성자 함수 Counter
increase와 decrease 메소드는 렉시컬 환경인 생성자 함수 Counter의 스코프에 속한 변수 counter를 기억하는 클로저이다.
생성자 함수가 생성한 객체(인스턴스)의 메소드는 객체의 프로퍼티에만 접근할 수 있는게 아니라 자신이 기억하는 렉시컬 환경의 변수에도 접근할 수 있다.
✔ counter는 this에 바인딩된 프로퍼티가 아니라 변수임
✔ 만약 this에 바인딩된 프로퍼티라면 public 프로퍼티이므로, 인스턴스를 통해 counter에 접근할 수 있어야 함. 그러나 undefined
✔ 생성자 함수 내에 선언된 counter는 외부에서 접근 불가능하다. 클로저는 접근 가능. (기억하기 때문)
▷ 클로저의 특징을 사용해서 클래스 기반 언어의 private 키워드를 흉내낼 수 있다.
④ 자주 발생할 수 있는 실수
(예시)
var arr = [];
for (var i = 0; i < 5; i++) {
arr[i] = function () {
return i;
};
}
for (var j = 0; j < arr.length; j++) {
console.log(arr[j]());
}
✔ 생각: arr[0] ~ arr[4]에 함수를 할당. 각 함수는 i를 반환함. 순차적으로 0 ~4를 반환하겠지?
✔ 현실: 5 5 5 5 5
✔ 이유: i가 전역변수이기 때문이다. 최종적으로 i는 5가 되며, arr에 저장된 함수를 실행할 때 i를 반환하기 때문에 5가 5번 반환된다.
// 클로저를 사용하여 해결
var arr = [];
for (var i = 0; i < 5; i++){
arr[i] = (function (id) {
return function () {
return id;
};
}(i));
}
for (var j = 0; j < arr.length; j++) {
console.log(arr[j]());
}
[ 코드 설명 ]
- 즉시 실행 함수는 매개변수 id에 인수 i를 전달받은 후 클로저를 반환.
- 즉시 실행 함수는 종료되고, 매개변수 id는 자유변수.
- 클로저 내용: id를 반환. 상위 스코프의 자유변수이므로 값이 유지됨.
#너무 어렵다............
# let 키워드 사용하면 문제는 해결됨. let 키워드는 블록 레벨 스코프를 지원하기 때문에..
# let 쓰자
[TIP] 즉시 실행 함수 문법
var result = (function(n1,n2){
return n1 + n2;
}(12,34));
console.log(result);
function 예약어 옆 괄호에 매개변수, 함수 끝 괄호에 실행 시 사용할 인수 전달
'프로그래밍 > js' 카테고리의 다른 글
lessons 20. 빌트인 객체 (0) | 2023.02.15 |
---|---|
lessons 19. 객체지향 프로그래밍 (0) | 2023.02.14 |
lessons 17. this 키워드 (0) | 2023.02.13 |
lessons 16. Strict mode (0) | 2023.02.12 |
lessons 15. 스코프 (0) | 2023.02.12 |