ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Scope/Closure/Hoisting
    카테고리 없음 2022. 3. 22. 17:19

    Scope


    scope란 쉽게 말하면 변수와 그 값이 유요한 범위를 말한다.

    scope는 크게 Local scope, Global scope, Lexical scope로 나뉜다.

    Local scope 에서 선언된 변수는 전역 변수에서 사용이 불가능하다.

    반대로 이야기 하면 Global scope는 스크립트 전체에 참조되는것을 의미한다.

     

    특정 함수내에서의 우선 순위는 지역 변수가 전역 변수 보다 우선순위가 높다.

    함수 내에서 전역 변수에 새로운 값을 할당해 준다면 전역 변수는 새로운 값으로 수정된다.

    따라서 웬만하면 전역변수는 수정이 안되는 const나 사용을 하지 않는편이 더 좋다.

     

    그리고 전역변수와 지역변수의 관계 에서 스코프 체인 이라는 개념이 나온다.

    내부 함수에서는 외부 함수의 변수에 접근 가능하지만 외부 함수에서는 내부 함수의 변수에 접근할 수 없다.

     

    아래의 코드를 보며 확인을 해보자

     

    var name = 'ckanywhere';
    function outer() {
      console.log('outer', name);
      function inner() {
        var myName = 'ckanywhere2';
        console.log('inner', name);
      }
      inner();
    }
    outer();
    console.log(myName)

     

    위의 코드에서 inner 함수에서는 name이라는 변수를 찾아야 하는데 inner 함수안에는 name이 없으니 outer함수에서 name을 찾는다.

    outer함수에서도 name 이 없어 global scope에서 name을 찾아 ckanywhere를 출력하게 될 것이다.

     

    하지만 myName을 콘솔로 출력을 하려 한다면 inner 함수에 있는 myName은 inner 함수에 포함된 내용이기에 undefined로 출력이 될 것 이다.

     

    마지막으로 Lexical scope는 함수를 처음 선언하는 순간, 함수 내부의 변수는 자기 스코프로부터 가장 가까운 곳(상위 범위에서)에 있는 변수를 계속 참조하게 된다.

     

    말이 좀 어려울 수도 있는데 하나씩 설명을 해보면 스코프는 함수를 호출할 때 생기는게 아니고 선언을 할 때 생긴다.

     

    다음의 예제를 보면

    var name = 'ckanywhere';
    function bar() {
      console.log(name);
    }
    
    function foo() {
      name = 'ckanywhere2';
      bar();
    }
    foo();

     

    여기서 foo 를 실행하면 당연히 ckanywhere2 가 출력 된다. 맨 처음 name이 ckanywhere로 초기화 되어서 선언이 되었고 foo()에서 ckanywhere2 로 값을 바꿔서 bar의 함수를 실행했다.

     

    그러면 다음의 예제는 어떻게 나올까?

    var name = 'ckanywhere';
    function bar() {
      console.log(name);
    }
    
    function foo() {
      var name = 'ckanywhere2';
      bar();
    }
    foo();

     

    ckanywhere 로 출력이 된다. bar 안의 name은 foo 안의 지역변수 name이 아니라, 전역변수 name을 가리키고 있는 겁니다. 이런 것을 lexical scoping 이라고 한다.

     

    다시 설명하자면 함수를 처음 선언하는 순간, 함수 내부의 변수는 자기 스코프로부터 가장 가까운 근처에 있는 변수를 계속 참조하게 된다. 

    그러니 bar를 실행 했을 때 실행 시점인 foo 함수에서 할당한 ckanywhere2가 나오는게 아니라 bar의 상단에 있는 ckanywhere를 출력 하게 된다.

     

    Closure


    클로저는 자신이 생성될 때의 환경을 기억하는 함수라고 이해하면 편하다.

    MDN에서는 이렇게 정의 한다.

     

    “A closure is the combination of a function and the lexical environment within which that function was declared.”
    클로저는 함수와 그 함수가 선언됐을 때의 렉시컬 환경(Lexical environment)과의 조합이다.

     

    이해가 어려울 수 도 있는데 함수가 선언됐을 때의 렉시컬 환경(Lexical environment) 라는 것이 주요 키워드 이다.

    좀 쉽게 설명을 하자면 클로저는 반환된 내부의 함수가 자신이 선언 되었을 때의 환경을 기억하여, 스코프 밖에서 호출이 되더라도 스코프에 접근 할 수 있는 함수이다.

    예제 코드를 먼저 보면

    function outerFunc() {
      var x = 10;
      var innerFunc = function () { console.log(x); };
      innerFunc();
    }
    
    outerFunc(); // 10

     

    여기서 outerFunc를 실행하면 x 값이 10으로 초기화가 되고 innerFunc에 x 값을 찍는 함수를 넣어준다. 그리고 innerFunc를 실행 하도록 한다.

    이때 내부함수 innerFunc는 자신을 포함하고 있는 외부함수 outerFunc의 변수 x에 접근할 수 있다. 이는 함수 innerFunc가 함수 outerFunc의 내부에 선언되었기 때문이다.

    아까 Lexical Scope에 대하여 설명을 하였는데, 스코프는 함수를 실행 할때 생성되는게 아니고 함수를 어디 선언 했냐에 따라 결정이 된다.

    위 예제의 함수 innerFunc는 함수 outerFunc의 내부에서 선언되었기 때문에 함수 innerFunc의 상위 스코프는 함수 outerFunc이다. 함수 innerFunc가 전역에 선언되었다면 함수 innerFunc의 상위 스코프는 전역 스코프가 된다.

     

    그렇다면 다음의 코드는 어떨까

     

    function outerFunc() {
      var x = 10;
      var innerFunc = function () { console.log(x); };
      return innerFunc;
    }
    
    var inner = outerFunc();
    inner(); // 10

     

    사실 동작 자체는 비슷해 보인다. 달라지는 것은 innerFunc 를 return 시키고 outerFunc의 값을 inner에 할당한 뒤 inner 를 실행 시킨다는 점이다. var inner = outerFunc() 여기서 outerFunc는 실행하고 종료가 되었다. 그러면 js 콜 스텍에서 outerFunc는 사라졌기에, 변수 x 도 사라져야 한다. 근데 inner에 담겨 있는 innerFunc 함수를 실행 시킬때 값이 잘 나오는 것을 확인 할 수 있다.

     

    이를 정리 해보면 특정 외부함수에서 내부함수를 포함하고 있을 때 외부함수 보다 내부함수가 더 오래 살아 있는 경우엔 외부함수 밖에서 호출을 하더라도 외부함수의 지역변수에 접근 할 수 있다는 뜻이다.

     

    Hoisting


    호이스팅은 코드가 실행 하기전 변수선언/함수선언이 해당 스코프의 최상단으로 끌어올려진것 같은 현상을 말한다.

    흔히들 변수와 함수가 끌어올려진다고 하는데 그것은 오해이다. 끌어올려지는 것 처럼 보이는 것 뿐이지 실제 값은 들어가지 않는다.

    크게 함수선언문, 함수표현식의 호이스팅이 있다.

    function getName() {
        console.log('name');
    }
    
    var name = function() {
       console.log('name');
    };

     

    javascript 에서 함수를 변수에 담을 수 있다. 

    이렇게 사용하는 것을 함수 표현식 이라고 한다.
    그리고 function getName() 과 같이 함수를 선언하는 것을 함수 선언문이라고 한다.

     

    호이스팅은 var와 함수선언문이 호이스팅 대상이 되며, let과 const 그리고 new 키워드를 썼을땐 호이스팅 대상에 포함이 되지 않는다.

     

    함수표현식에서 호이스팅은 다음과 같다.

    count();
    
    var count = function() {
        console.log('count');
    }

    이 코드를 실행 하면 TypeError가 난다 그 이유인 즉슨 javascript에선 이렇게 해석 하기 때문이다.

     

    var count;    // undefined
    
    count();      // count는 함수가 아닌데 왜 함수를 호출
    
    var count = function() {
        console.log('count');
    }

     

    count를 최상단에 끌어와 undefined의 상태로 min-heap에 저장을 해놓고 count()를 실행한다.

    이때 count의 값은 undefined인데 함수를 호출 해서 TypeError가 난다.

     

    그렇다면 다음의 코드는 어떨까

    var count = function() {
        console.log('count');
    }
    
    count();

    정상작동이 된다. 왜냐하면 js 에선 이렇게 해석 하기 때문이다.

     

    var count;    //undefined
    
    var count = function() {
        console.log('count');
    }
    
    count();

     

    count를 min-heap에 저장하고 count를 함수로 초기화 한뒤 해당 함수를 실행한다.

    정상적인 프로세스 이다.

     

    그렇다면 함수선언문의 호이스팅은 어떨까

    count();
    
    function count() {
        console.log('count');
    }
    
    
    function count2() {
        console.log('count');
    }
    
    count2();

    두 함수 모두 정상 작동이 된다.

    호출이 함수선언문의 위에 있든 아래쪽에 있든 함수 선언문은 호이스팅 영향으로 끌어올려지기 때문이다.

    댓글

Designed by Tistory.