본문 바로가기

Javascript

javascript - closure(클로저) 이론과 예시

728x90

들어가기 앞서

자바스크립트에는 실행 컨텍스트 라는 개념이 있으며, 이 실행 컨텍스트는 생성되었다가 사라질 수 있습니다.

함수 실행을 마치고나면 call stack에 있던 해당 함수에 대한 실행 컨텍스트는 사라질 것입니다.

 

const secureBooking = function() {
    let passengerCount = 0;

    return function() {
        passengerCount++;
        console.log(`${passengerCount} passengers`)
    }
}

// 함수를 return 받았기 때문에 booker도 함수가 될것이다
const booker = secureBooking();

위의 코드에서, "secureBooking" 이라는 함수를 생성하고 return 값으로 함수를 반환하도록 했습니다.

그리고 booker 에 해당 return 값인 함수를 저장했습니다.

 

booker를 실행 할 때마다 passengerCount 값은 계속 증가하는지 보겠습니다.

booker();
booker();
booker();

정의대로라면 secureBooking 함수를 실행하고나면 실행 컨텍스트가 call stack에서 사라지고,

변수 환경 또한 사라져서 passengerCount 변수에 접근할 수 없어야 합니다.

 

그러나 제대로 출력이 되는 것을 볼 수 있었는데, 이것이 클로저가 하는 역할입니다.

 


클로저(closure)

클로저가 있기 때문에 함수가 정의된 시점의 모든 변수를 기억할 수 있습니다.

 

booker();

 이 booker 함수가 실행되기 위해,

1) 먼저 call stack에 변수 환경이 비어있는 채로 실행 컨텍스트가 생성됩니다.

전역 스코프에서 실행되었기 때문에 여전히 passengerCount 변수에 접근할 수 없습니다.

 

2) 그러나 booker 함수는 클로저 라는 기능 덕분에

"해당 함수(secureBooking) 가 정의된 시점의 모든 변수 환경에 접근할 수 있습니다."

이는 해당 실행 컨텍스트가 사라져도 적용됩니다.

 

즉 클로저는

정확히 함수가 생성되었을 시점의 함수에 붙어있는 변수환경

이라고 할 수 있습니다.

클로저 덕분에 함수가 변수에 대한 연결을 계속해서 유지할 수 있는 것입니다.

 


함수가 사람이고, 배낭이 클로저이며, 변수환경이 물건들이라면,

사람이 어디를 가든 배낭을 가져가기 때문에 그 안에 들어있는 변수환경 또한 계속해서 가지고 다니는 것입니다.

 

그리고 클로저는 기본적으로 스코프 체인(scope chain) 보다 우선순위가 높습니다.

// 전역 스코프에 선언
let passengerCount = 123;

const secureBooking = function() {
    let passengerCount = 0;

    return function() {
        passengerCount++;
        console.log(`${passengerCount} passengers`)
    }
}

const booker = secureBooking();

booker();
booker();
booker();

따라서 전역 스코프에 passengerCount 변수가 이미 있더라도

클로저를 먼저 살펴보기 때문에 아까와 같은 결과값이 출력됩니다.

 

그리고 클로저는 함수의 내부 속성일 뿐이기 때문에, 클로저로부터 변수를 가져와서 사용할 순 없습니다.

그저 클로저 기능이 기본적으로 있기 때문에 우리에게 보여지게 되는 것입니다.

 

 

그리고 클로저를 살펴보고싶다면, console.log 대신 console.dir 를 사용하면 됩니다.

console.dir(booker);

 


예시

1)

let f;

const g = function() {
    const a = 23;
    f = function() {
        console.log(a * 2);
    }
}

g();
f();

 

g 함수를 실행하면, f 변수에 새로 함수가 저장되는데,

이때 앞서 말한대로 g 함수가 실행이 끝나고 실행 컨텍스트가 삭제 되더라도, 변수 환경은 계속해서 연동되어 있기

때문에 정상적으로 출력이 될 것입니다.

 

그렇다면 h 함수를 선언하고 f에 재할당 한다면 어떻게 될까요?

const g = function() {
    const a = 23;
    f = function() {
        console.log(a * 2);
    }
}

const h = function() {
    const b = 777;
    f = function() {
        console.log(b * 2);
    }
}

// g 함수에 대한 실행 컨텍스트는 제거되었지만 
g();
// a 변수는 앞서 말한대로 배낭 안에 있습니다
f();

// 재할당
// 이전 클로저는 사라짐
h();
f();

이전 클로저는 삭제되며 새로운 클로저가 생성되는 것을 볼 수 있습니다.

이렇듯 클로저는 재할당이 가능하다는 특징이 있습니다

 

2) 타이머

타이머를 사용하여 클로저가 존재하는지 확인해보겠습니다

const boardPassengers = function(n, wait) {
    const perGroup = n / 3;

    setTimeout(() => {
        console.log(`${n}명과 함께 출발했음`);
        console.log(`3 그룹, ${perGroup}명의 승객`)
    }, 1000)

    console.log(`${wait}초 후 시작`)
}

// 스코프 체인이 우선순위가 높다면 아래의 변수를 사용할 것이다
const perGroup = 1000;
boardPassengers(180, 3);

 

역시 타이머를 적용해도 클로저는 남아있어서 변수 환경이 그대로 연결되어있는 것을 확인할 수 있으며,

perGroup 변수를 전역스코프에 선언해도

클로저의 우선순위가 스코프 체인보다 높기 때문에 전역변수 대신 함수 내에 있는 변수가 사용된 것을 볼 수 있습니다

728x90