클로저란?
클로저는 함수와 함수가 선언된 어휘적 환경의 조합이다.
클로저를 이해하려면 자바스크립트가 어떻게 변수의 유효범위를 지정하는지(Lexical scoping)를 먼저 이해해야 한다.
다음예제를 확인해보자
function init() {
var name = "Mozilla"; // name은 init에 의해 생성된 지역 변수이다.
function displayName() { // displayName() 은 내부 함수이며, 클로저다.
alert(name); // 부모 함수에서 선언된 변수를 사용한다.
}
displayName();
}
init();
init()은 지역 변수 name과 함수 displayName()을 생성한다.
displayName()은 init() 안에 정의된 내부 함수이며 init() 함수 본문에서만 사용할 수 있다.
여기서 주의할 점은 displayName() 내부엔 자신만의 지역 변수가 없다는 점이다.
그런데 함수 내부에서 외부 함수의 변수에 접근할 수 있기 때문에 displayName() 역시 부모 함수 init()에서 선언된 변수 name에 접근할 수 있다.
만약 displayName()가 자신만의 name변수를 가지고 있었다면, name대신 this.name을 사용했을 것이다.
만약 const를 썼더라면 this를 무조건 써야한다.
하지만 실행해 보면 잘 출력되는것을 확인 할 수 있다.
let과 const를 사용한 범위 지정
ES6 이전 전통적인 JavaScript에는 함수 스코프와 전역 스코프 두 가지만 존재했다.
var로 선언한 변수는 함수 내부 또는 외부에서 선언되었는지에 따라 함수 스코프 또는 전역 스코프를 가진다.
이때, 중괄호로 표시된 블록이 스코프를 생성하지 않는다는 점에서 혼란을 일으킬 수 있다.
아래 예시를 확인해보자
if (Math.random() > 0.5) {
var x = 1;
} else {
var x = 2;
}
console.log(x);
C나 Java와 같이 블록이 스코프를 생성하는 언어의 경우,
위 코드의 console.log 라인에서 x가 어떤 블록 스코프에도 포함되지 않기 때문에 에러가 발생해야 할 것이다.
그러나 블록은 var로 선언한 변수에 대해 스코프를 생성하지 않기 때문에 여기서 var 명령문은 전역 변수를 생성한다.
이것을 클로저와 함께 사용했을 때 어떤 버그가 발생할 수 있는지 실제 예제가 아래 소개되어 있다.
ES6에서 JavaScript는 블록 스코프 변수를 생성할 수 있도록 let과 const 선언과 함께 시간상 사각지대 등을 도입했다.
if (Math.random() > 0.5) {
const x = 1;
} else {
const x = 2;
}
console.log(x); // ReferenceError: x is not defined
요약하자면, ES6부터 블록은 스코프로 취급되기 시작했지만,
이는 let과 const로 변수를 선언했을 때만 유효하다.
또한, ES6에서 모듈을 도입하면서 또 다른 스코프를 제공하게 되었다.
추후 소개하겠지만, 클로저는 이 모든 스코프의 변수를 캡처할 수 있다.
이제 다음 예제를 보자:
function makeFunc() {
var name = "Mozilla";
function displayName() {
alert(name);
}
return displayName;
}
var myFunc = makeFunc();
//myFunc변수에 displayName을 리턴함
//유효범위의 어휘적 환경을 유지
//myFunc 자료형이 funtion이 된다.
myFunc();
//리턴된 displayName 함수를 실행(name 변수에 접근)
이 코드는 바로 전의 예제와 완전히 동일한 결과가 실행된다.
하지만 흥미로운 차이는 displayName()함수가 실행되기 전에 외부함수인 makeFunc()로부터 리턴되어
myFunc 변수에 저장된다는 것이다.
한 눈에 봐서는 이 코드가 여전히 작동하는 것이 직관적으로 보이지 않을 수 있다.
몇몇 프로그래밍 언어에서, 함수 안의 지역 변수들은 그 함수가 처리되는 동안에만 존재한다.
makeFunc() 실행이 끝나면(displayName함수가 리턴되고 나면) name 변수에 더 이상 접근할 수 없게 될 것으로 예상하는 것이 일반적이다.
하지만 위의 예시와 자바스크립트의 경우는 다르다. 그 이유는 자바스크립트는 함수를 리턴하고, 리턴하는 함수가 클로저를 형성하기 때문이다. 클로저는 함수와 함수가 선언된 어휘적 환경의 조합이다.
이 환경은 클로저가 생성된 시점의 유효 범위 내에 있는 모든 지역 변수로 구성된다.(var로 변수를 선언했기 때문이다.)
첫 번째 예시의 경우, myFunc은 makeFunc이 실행 될 때 생성된 displayName 함수의 인스턴스에 대한 참조다.
displayName의 인스턴스는 변수 name 이 있는 어휘적 환경에 대한 참조를 유지한다.
이런 이유로 myFunc가 호출될 때 변수 name은 사용할 수 있는 상태로 남게 되고 "Mozilla" 가 alert 에 전달된다.
다음은 조금 더 흥미로운 예제인 makeAdder 함수이다
function makeAdder(x) {
var y = 1;
return function(z) {
y = 100;
return x + y + z;
};
}
var add5 = makeAdder(5);
var add10 = makeAdder(10);
//클로저에 x와 y의 환경이 저장됨
console.log(add5(2)); // 107 (x:5 + y:100 + z:2)
console.log(add10(2)); // 112 (x:10 + y:100 + z:2)
//함수 실행 시 클로저에 저장된 x, y값에 접근하여 값을 계산
add5와 add10은 둘 다 클로저이다.
이들은 같은 함수 본문 정의를 공유하지만 서로 다른 맥락(어휘)적 환경을 저장한다.
함수 실행 시 add5의 맥락적 환경에서 클로저 내부의 x는 5 이지만 add10의 맥락적 환경에서 x는 10이다.
또한 리턴되는 함수에서 초기값이 1로 할당된 y에 접근하여 y값을 100으로 변경한 것을 볼 수 있다.
(물론 x값도 동일하게 변경 가능하다.)
이는 클로저가 리턴된 후에도 외부함수의 변수들에 접근 가능하다는 것을 보여주는 포인트이며
클로저에 단순히 값 형태로 전달되는것이 아니라는 것을 의미한다.
js의 접근제어자라고 생각하면 된다.
지역변수이지만 전역변수처럼 사용한다.(변수의 보호가 목적)
클로저 를 사용하여 전역 변수를 로컬로 만들 수 있습니다 .
함수가 개인변수를 가질 수 있게 된다.


클로저 연구해보기
const add를 const add = (); 형식으로 선언한뒤
console.log(add); 하면 return 된 function {} 진행문(함수 표현식)이 나온다.
여기까지 하면 카운터변수가 0 할당이 되고 결국 add는 함수가 된다.
let counter는 블록 스코프 영향을 받는다.
큰 function 스코프안에 블록스코프가 있으므로 안에 있는 블록스코프도 똑같은 counter 변수로 받는다.
(변수의 유효범위가 중요한 이유다.)
add(); 함수를 호출 될때마다 1씩 증가한다.
이를 JavaScript 클로저라고 한다.
자바스크립트에는 class의 개념이 약하다
하지만 클로저나 this키워드를 이용하여
다른언어들과 같이 class 기능같이 쓸 수 있다.
하지만 단점이 있다.
위예제는 가비지 컬렉터가 삭제할 수 없다.
그래서 브라우저를 계속 지속시 할당 메모리가 커질 수 밖에 없다
참고사이트 : https://www.w3schools.com/js/js_function_closures.asp
'Web > javascript' 카테고리의 다른 글
| arguments 객체 (0) | 2023.01.16 |
|---|---|
| 쿠키 (0) | 2023.01.13 |
| JS Type 형 변환 테이블 (0) | 2023.01.10 |
| JS if문 , switch문 , 반복문 (0) | 2023.01.10 |
| js 디버깅 브라우저(크롬)에서 (0) | 2023.01.10 |