이전에 조사했었지만,, 너무너무 어렵게 느껴졌던 개념이다.
이번 기회에 closure를 부셔버리겠어,, 부서지는건 나일지도
👀 Closure란?
클로저는 주변 상태(어휘적 환경)에 대한 참조와 함께 묶인(포함된) 함수의 조합입니다
라고 MDN에 나와있지만 정말 무슨 말인지 이해가 안된다,,
코드를 살펴보자.
function doSomething() {
const x = 10;
function sum(y) {
console.log(x+y);
}
return sum;
}
const something = doSomething();
something(5); // 15
doSomething 함수는 sum 함수를 리턴하고 있다.
sum 함수에서는 doSomething 함수의 x 변수를 참조하고 있다.
doSomething 함수는 이미 실행이 끝났음에도,
something(5)를 실행하면 doSomething의 변수인 x에 접근 가능하다.
sum 함수에서 x 변수를 기억하고 있는 것이다.
이렇게 함수가 생성될 당시의 외부 변수를 기억하는 것을 클로저라고 한다.
👀 Lexical Scope란?
Closure를 이해하기 전에 Lexical Scope부터 이해해보자.
var num = 100;
function outter() {
var num = 1;
function inner() {
console.log(num)
}
inner();
}
outter(); // 1
outter는 전역 변수가 아닌, outter의 지역 변수인 num을 출력한다.
함수가 호출될 때가 아닌, 선언될 때 scope가 정해지는 것을 Lexical Scope라고 한다.
Execution context는 말 그대로 코드가 실행되는 환경을 의미한다.
Execution context는 함수에 의해서 생기는 것이고, 그림과 같이 3가지가 있는데 우리는 그 중에 Lexical Envrionment를 볼 것이다.
Lexical Environment는 식별자(변수, 함수 등)의 정보를 저장하는 어휘적 환경이다.
Environment Record는 변수와 함수 선언을 저장한다.
Execution context가 가장 먼저 하는 일이 Environment Record 정보를 수집하는 것이다.(==호이스팅)
Outer Environment는 현재 Lexcial Environment가 참조하는 상위 Lexical Envrionment를 가리킨다.
Lexical Environment에 있는 Environment Record와 Outer Environment를 통해 Closure에 대해 알아보자!
👀 Closure 동작 방식
function sum(x) {
return function(y) {
return x+y;
}
}
const add = sum(2);
console.log(add(7));
코드를 실행하기 전에, 전역 Lexical Environment를 생성한다.
간단하게 Environment Record를 Record로, Outer Environment를 Outer라고 부르겠다.
전역 Lexical Environment의 Record에 sum 함수와 add 변수를 기록하고,
Outer에는 null이 할당된다.
이후, 콜스택에 Global Lexcial Envrionment를 넣는다.
이제 코드가 실행되고, add에 값을 할당해주기 위해 sum 함수가 호출된다.
sum 함수도 Lexcial Environment를 생성하고
Record에 sum 함수 내부 변수인 x와 익명함수가 기록되고 Outer에는 상위 스코프인 Global Lexcial Envrionment가 할당된다.
콜 스택에 sum 함수의 Lexcial Environment를 넣는다.
여기서 주의할 점은 sum의 Outer는 함수가 호출될 때가 아닌, 함수가 선언되는 시점에 결정된다는 것이다.
이제 매개변수로 2를 넘겨주면 sum 함수의 Record에 있는 x에 2를 기록한다.
이후 실행할 코드가 없으니, sum 함수의 Lexcial Environment는 콜 스택에서 빠져나온다.
다시 Global Lexcial Environment로 돌아와서
Record 안의 add 변수에 sum 함수의 리턴 값인 익명함수를 할당해준다.
코드의 마지막 줄인 console.log를 실행하기 위해서 add 함수를 호출한다.
add 함수도 Lexcial Environment를 생성하고
Record에 y 변수를 기록하고 Outer에는 sum의 Lexcial Environment가 할당된다.
하지만 현재 콜 스택에는 Global Lexcial Environment만 존재하는데?
여기서 클로저 개념이 나오는 것이다.
위에서도 말했듯이, 함수의 호출 시점이 아닌, 함수가 선언되는 시점에 Outer를 결정하기 때문에
add 함수의 Outer에는 sum 함수의 Lexcial Envrionment가 할당될 수 있는 것이다.
add 함수의 Lexcial Environment가 콜 스택에 들어오게 된다.
add 함수의 매개변수로 7을 넘겨주면 add 함수의 Record에 있는 y 변수에 7을 기록한다.
이제 return x+y가 실행되는데, add 함수의 Record에 존재하지 않는 변수인 x를 마주한다.
자바스크립트에서는 변수를 탐색할 때,
자신의 Record부터 탐색하고 없다면 계속해서 상위(Outer)로 올라가다가
Global Lexcial Environment Record에도 존재하지 않으면 ReferenceError가 발생한다.
add 함수의 Outer인 sum 함수의 Record에 변수 x=2가 존재하기 때문에
x=2, y=7이 되고 9를 리턴해준다.
add 함수의 Lexcial Environment는 콜 스택을 빠져나오고 콜 스택에는 Global Envrionment만 남게 된다.
마지막으로 콘솔 창에 9를 출력하고 Global Environment도 콜 스택에서 빠져나오면 프로그램은 종료된다.
sum의 Lexical Envrionment는 콜 스택에 존재하지 않지만,
add 함수의 Outer Environment에 sum의 Lexical Envrionment가 할당되어 있어서 변수 x에 접근할 수 있었다.
이렇게 변수가 사라지지 않는 현상을 클로저 현상이라 한다.
👀 Closure의 장점
- 정보 은닉
객체 지향 프로그래밍에서 private 키워드 사용과 같은 효과를 낼 수 있다. - 전역 변수 사용 억제
전역 변수는 어디에서든 접근 가능하기 때문에, 최대한 전역변수를 줄이는 것이 좋다. - 데이터 보존
외부 함수의 실행이 끝나더라도 외부 함수 내의 변수를 사용할 수 있으므로
특정 데이터를 스코프 안에 가둬 놓고 계속 사용할 수 있다.
👀 Closure의 단점
- 메모리 누수
내부에서 외부 변수를 참조하면서 가비지 컬렉션이 일어나지 않게 된다.
=> 클로저가 더 이상 쓰이지 않는 시점에서 null이나 undefined를 할당하는 방법으로 해결할 수 있다. - 성능 저하
클로저는 함수 내부 변수에 접근하기 위해 스코프 체인을 거쳐야하기 때문에, 약간의 성능저하가 일어날 수 있다.
'JavaScript' 카테고리의 다른 글
JS Class (0) | 2024.08.13 |
---|---|
JS Prototype (0) | 2024.08.08 |
JS This (0) | 2024.08.07 |
JS 동작원리 (0) | 2024.08.05 |
[JS]Reduce 함수 (4) | 2024.04.12 |