ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Javascript] Scope / Closure / Hoisting (스코프와 클로저 그리고 호이스팅)
    언어, 프레임워크/Javascript & Typescript 2020. 7. 17. 14:35

     

    이전 블로그에서 옮긴 글입니다 https://blog.naver.com/jaemin-jeong?Redirect=Update&logNo=221877574892

    틀린 내용을 발견하시면 댓글 부탁드립니다!


    Scope (스코프)

    변수 접근 규칙에 따른 유효 범위, 좀 더 쉽게 말하면 변수와 그 값이 유효한 범위라고 할 수 있다. Local scope(지역 스코프)에서 선언된 변수는 Global scope(전역 스코프)에서 사용이 불가능하다.

    전역에서 선언된 변수 a는 지역 스코프에서 참조할 수 있으나, 지역에서 선언된 변수 b는 바깥(전역) 스코프에서 참조할 수 없다. 특정 함수 내에서 선언된 지역 변수는 전역 변수보다 우선순위가 높다 (전역 변수는 전역 스코프를 갖는다). 함수 내에서 전역 변수에 새로운 값을 할당해 준다면 전역 변수는 새로운 값으로 수정된다. 참고로 전역 변수는 웬만하면 사용 안 하는 게 좋다. 전역 변수를 사용하게 되면 변수 이름이 중복되거나, 의도치 않게 변수 재할당이 발생할 수도 있어 코드가 길어질수록 파악하기 힘들어지기 때문이다.

     

    위에서 말한 전역 변수와 지역 변수의 관계를 통해 Scope chain(스코프 체인)에 대해 알아보자

    위 그림은 전역 스코프 global, 전역 스코프 안에 중첩 선언된 함수들인 outer2, outer1, inner를 표현한 것이다. inner 함수에서 변수 A를 사용해야 된다고 가정하자. 만약 inner 함수에 변수 A가 선언 안 되어있다면? 그림처럼 1 → 2 → 3 → 4 순서대로 변수 A를 찾는다. 이렇게 내부에서 외부로 올라가며 연결되는 관계를 스코프 체인이라고 한다.

     

    Scope는 Function level scope(함수 레벨 스코프)와 Block level scope(블록 레벨 스코프)로 나뉜다. 원래 자바스크립트에서의 Scope는 Function level scope이었다. Function level scope란 함수 영역을 단위로 매겨 해당 함수 안에서 선언된 변수를 함수가 끝나기 전까지 사용하는 것을 말한다. var 키워드를 사용해 Function level scope 변수를 선언해 사용하다 보니 메모리 누수, 디버깅이 어려움, 가독성 하락 등의 문제점이 발생했다. 이와 같은 문제점을 해결하기 위해 ES6에서 let, const 키워드가 나왔으며, 이를 통해 Block level scope를 사용할 수 있게 되었다. Block level scope는 중괄호로 시작하고 끝나는 영역을 기준으로 단위를 매기기 때문에, 특정 중괄호 안에 선언된 변수는 해당 중괄호 및 하위 중괄호 안에서만 사용이 가능하다.

     

    추가로 Lexical scope(렉시컬 스코프)에 대해서도 정리해본다. - 클로저 개념 이해하는데 필요하기 때문

    아래는 함수 func1, func2를 정의하고 실행한 결과이다.

    var result = 'lexical';
    
    function func1(){
      var result = 'dynamic';
      func2();
    }
    
    function func2(){
      console.log(result);
    }
    
    func1(); // lexical
    func2(); // lexical

    함수 func1, func2의 실행 결과가 모두 lexical이다. 여기서 알 수 있는 것은, 함수의 상위 스코프가 결정되는 기준은 함수가 선언된 위치다. 만약 함수 실행(호출) 위치를 기준으로 상위 스코프가 결정된다면, 함수 func1안에서 실행되는 함수 func2의 상위 스코프는 함수 func1이 될 것이고, func2에서 console.log(result)는 func1안에서 선언된 result, 즉 dynamic을 출력할 것이다. 여기서 함수 선언 위치를 기준으로 상위 스코프를 결정하는 게 Lexical scope(=Static scope), 함수 호출 위치를 기준으로 상위 스코프를 결정하는 게 Dynamic Scope이다. 자바스크립트 및 다른 프로그래밍 언어들 대부분이 Lexical scope를 따른다고 한다.

     

     

    Closure (클로저)

    자신이 생성될 때의 환경을 기억하는 함수. 클로저는 함수와 함수가 선언된 어휘적 환경(Lexical scoping)의 조합이다. 풀어서 이야기하면 클로저는 반환된 내부 함수가 자신이 선언됐을 때의 환경인 스코프를 기억하여, 스코프 밖에서 호출돼도 스코프에 접근할 수 있는 함수다. poiemaweb에 있는 글을 참고해 아래와 같은 짧은 코드를 작성했다.

    // outerFunc함수 안에서 innerFunc함수를 선언하고 실행
    function outerFunc(){
      let name = 'jaemin';
      function innerFunc(){
        console.log(name);
      }
      innerFunc();
    }
    
    // outerFunc함수 실행. 결과: jaemin
    outerFunc();

    outerFunc 함수 안에 name 변수 선언, innerFunc 함수 선언, 호출이 모두 있기 때문에, outerFunc 함수를 실행했을 때 jaemin이라는 결과가 잘 나온다.

    // outerFunc함수 안에서 innerFunc함수를 선언하고 '반환'
    function outerFunc(){
      let name = 'jaemin';
      function innerFunc(){
        console.log(name);
      }
      return innerFunc;
    }
    
    let result = outerFunc();
    // result 실행. 결과: jaemin
    result();

    처음 코드와 달라진 것들은 innerFunc를 반환한 점, outerFunc 함수 실행 값을 변수 result에 할당한 뒤 result를 실행한 점이다. let result = outerFunc(); 이 부분에서 outerFunc 함수는 실행되고 종료되었다. 즉 콜 스택에서 outerFunc는 사라졌기 때문에 변수 name도 없어져야 된다. 하지만 변수 result에 할당된 innerFunc 함수를 실행시켰을 때 변수 name 값이 잘 출력되는 것을 확인할 수 있다. 즉, 어떠한 외부 함수가 특정 내부 함수를 포함하고 있을 때, 외부 함수보다 내부 함수가 더 오래 살아있는 경우에는 외부 함수 밖에서 내부 함수를 호출해도 외부 함수의 지역 변수에 접근할 수 있다는 뜻이다. 클로저가 사용되는 경우는 아래와 같다.

    1. 현재 상태를 기억하고 변경된 최신 상태를 유지해야 될 때
    2. 전역 변수의 사용을 줄이고 싶을 때
    3. 어떠한 정보를 숨기고 싶을 때

     

    Hoisting (호이스팅)

    호이스팅은 한 단어로 말하면 끌어올리기라고 할 수 있다. 호이스팅은 변수 호이스팅과 함수 호이스팅으로 나뉜다. 변수 호이스팅은 변수 선언 및 초기화 시, 변수가 유효 범위의 최상단으로 올려져 해석되는 것이고, 함수 호이스팅도 같은 맥락으로 함수 선언 시 함수가 유효 범위 내에서 최상단으로 올려져 해석되는 것이다. 다만 변수 호이스팅은 var 키워드를 사용해 선언했을 때만 이루어진다(let, const 키워드를 사용하면 호이스팅이 발생하지 않음). 또한 함수 호이스팅은 함수 선언문일 경우에만 가능하며, 함수 표현식 또는 new 키워드를 사용할 경우에는 호이스팅이 불가능하다.

    // 변수 호이스팅
    console.log(name); // undefined
    var name = 'jaemin';
    console.log(name); // jaemin

    첫 번째 console.log(name)에서 undefined가 나오는 이유는 위 구문을 자바스크립트 엔진에서 아래와 같이 해석하기 때문이다

    var name;
    console.log(name); // 선언
    name = 'jaemin';
    console.log(name); // 할당

    변수 name이 맨 윗줄로 호이스팅되어 선언이 이루어지고 console.log(name)을 했기 때문에 undefined가 출력되는 것이며, 변수에 값을 할당하는 것은 실제로 변수 선언을 했던 부분에서 이루어진다.

    // 함수 선언문은 아래와 같은 형태이다. 호이스팅이 발생한다.
    function func1(){
     ...
    }
    // 함수 표현식은 아래와 같은 형태이다. 호이스팅이 발생하지 않는다.
    let func2 = function(){
     ...
    }

     

    호이스팅의 우선순위

    • var 변수 선언과, 함수 선언의 호이스팅 → 변수 선언 > 함수 선언
      변수 선언의 우선순위가 더 높아 함수 선언보다 위로 호이스팅 됨
    • 값이 할당된 변수와, 동일한 이름을 갖는 함수의 호이스팅 → 변수 > 함수
      변수가 함수를 덮어씀
    • 값이 할당되지 않은 변수와, 동일한 이름을 갖는 함수의 호이스팅 → 변수 < 함수
      함수가 변수를 덮어씀

     

     

     

     


    참고 자료

    댓글

jaejade's blog ٩( ᐛ )و