ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [JavaScript] 클로저(Closure)란?
    JavaScript 2022. 2. 10. 20:20
    728x90

     

    자바스크립트에서 어떠한 함수가 존재할 때 해당 함수의 유효 범위는 어디서 실행됐느냐에 따라 달라지는 게 아니라 정의된 위치에 따라서 달라진다. 함수를 호출하는 경우 함수 외부, 내부 가리지 않고 어디서든 작성 가능한 dynamic scope에 해당한다. 그러나, 함수는 딱 한번 정의를 해놓은 후 정적인 위치에 있기에 static scope(lexical scope)를 따르고 있다.

     

    여기서 언급되는 scope는 범위를 의미하는데 구체적으로는 "어떠한 변수에 접근 가능한 범위" 라고 할 수 있다. scope를 크게 보면 global, local로 나뉜다. 클로저를 알기 이전에 필요한 부분을 먼저 살펴보겠다.

     

    전역 범위(global scope)는 내부뿐만 아니라 외부에서도 해당 변수에 접근할 수 있다는 걸 뜻하며, 지역 범위(local scope)는 내부 지역에서만 접근 가능하기에 그 지역을 벗어나면 접근이 불가능하다.

     

     

    다음은 global scope, local scope의 예시로 이해를 돕기 위해 코드와 함께 보면 좋을 것 같다.

    var s1 = 2021; // global scope
    
    function print() { // local scope
        var s1 = 2022; 
        console.log(s1);
    }
    
    print(); // 2022
    console.log(s1); // 2021

     

    위의 코드에서는 똑같은 변수가 global과 local(function scope)에 선언되었는데 실행을 했을 때 print 함수가 먼저 호출이 되었으므로 local scope에 있는 s1의 값이 출력되고 이후 global scope에 들어간 데이터가 출력된다. 참고로, local scope에 있는 s1은 함수가 종료된 후 소멸된다.

     

     

    만약, local scope에 s1이라는 변수가 선언되지 않았는데 print 함수를 호출하는 경우엔 어떻게 될지 살펴보자.

    var s1 = 2021; // global scope
    
    function print() { // local scope
        console.log(s1);
    }
    
    print(); // 2021

     

    에러가 뜨지않고, 전역 범위에 있는 변수가 출력이 되는 것을 확인할 수 있다. 이는 우선적으로 자신의 함수 내부에 s1이라는 변수가 있는지 확인을 한 후 해당 scope에 없기때문에 scope chaining이 일어나 외부에서 변수를 찾으므로 문제없이 정상적인 결과가 나오는 것이다.

     

     

    전역 범위와 지역 범위에 대해 알아보았으니 이제 동적 범위(dynamic scope)와 정적 범위(static scope/lexical scope)에 대해 간단히 살펴보고 클로저(closure)라는 것을 이해해보자.

     

    먼저 static scope(lexical scope)는 함수 선언 위치에 따라 상위 범위가 결정되는 것이고, 함수의 호출 위치에 따라서 상위 범위가 정해지는 게 dynamic scope이다. 

     

    다음의 코드는 static scope 을 보여주는 예시이다.

    var test = 30;
    
    function fn1() {
        var test = 333;
        fn2();
    }
    
    function fn2() {
        console.log(test);
    }
    
    fn1(); // 30
    fn2(); // 30

     

    fn1 함수 내부에서 fn2 함수를 호출했을 때 test가 같은 지역 범위 안에 333으로 재할당되어 333이 출력되지 않을까 생각할 수도 있으나 맨 처음에 언급하였듯이 함수의 유효범위는 호출로 상위 범위가 정해지는 게 아니라 정의된 위치에 따라 결정되는 것이기에 fn2의 상위 범위는 전역 범위를 가리키므로 모두 30이 출력되는 것이다.

     

    자바스크립트뿐만 아니라 Java, C 등 여러 프로그래밍 언어들이 이러한 lexical scope를 많이 따른다고 한다.

     

     

     

    위의 내용을 이해하였다면 Closure의 개념은 쉽게 파악할 수 있는데 특정 함수를 함수 내부에 적용했을 때 그 함수의 부모 함수의 scope에 접근할 수 있다는 것이다.

     

    Closure

    함수와 함수가 선언된 정적인 조합으로써, 함수를 다른 객체에 일반적으로 적용할 수 있는 연산을 모두 지원해주는 일급 객체로 취급하고 그 함수가 선언된 환경과의 관계를 말한다.

     

    이 관계를 풀어서 간단히 해석한다면 함수가 만들어지는 시점에서 부모 함수가 갖고 있는 scope 내 변수들을 그 안에 폐쇄되어있는 함수가 가지고 있다는 것이다. 

     

    즉, 부모 함수 안에서 자식 함수를 정의하면 자식 함수를 어디서 호출하든 자식 함수 내부에서 부모 함수의 변수(scope)에 접근가능하다는 의미가 된다.

     

    코드를 통해 클로저를 확실하게 이해하여 보자.

    let l0 = 'l0';
    
    function fn1() {
        function fn2() {
            function fn3() {
                let l3 = 'l3';
                console.log(l0, l1, l2, l3);
            }
            
            let l2 = 'l2';
            console.log(l0, l1, l2);
            fn3();
        }
        
        let l1 = 'l1';
        console.log(l0, l1);
        fn2();
    }
    fn1();

     

    fn1 함수를 호출한 후, 지역 범위에 선언된 l1과 전역 범위에 선언된 l0를 출력해주었고, 그 다음에 fn2를 호출해주는데 이때 fn2의 정의된 위치가 fn1 함수 범위 내에 존재하므로 상위 범위 즉, 부모 함수는 fn1이 되면서 l1에도 접근 가능하기에 l0, l1, l2를 차례대로 출력해주는 것을 확인할 수 있다.  

     

    이후 같은 맥락으로 fn3 함수를 호출했을 때 정의된 위치를 보고 부모 함수 fn2에 있는 변수 l2에 접근할 수 있게되어 l0 부터 l3까지 정상적으로 출력이 가능하다. 이는 lexical scope를 따르고 있기에 가능한 것이고 dynamic scope를 따른다면 결과가 달라질 것이다.

     

    같은 변수가 두 개 이상 다른 유효 범위에 존재하거나 중첩된 function scope에 여러 개의 변수가 있을 때, 함수가 정의된 위치 또는 함수 내부에서 또 다른 함수를 호출하는 경우에 따라서 어떠한 멤버에 접근을 할 것이고 어떤 결과가 나올 수 있을지에 대해 예측하기 까다로운 문제가 있었으나 이번 정리를 통해 확실히 익힐 수 있었다. 

    728x90

    'JavaScript' 카테고리의 다른 글

    [JavaScript] Promise에서 한 걸음 더, async & await  (0) 2022.02.15
    [JavaScript] 비동기와 Promise  (0) 2022.02.13
    [JavaScript] 편리함을 위한 ES6  (0) 2022.02.04
    [JavaScript] ES6 Module  (0) 2022.02.04

    댓글

Designed by Tistory.