본문 바로가기

Backend/Javascript

Function Scope(ES5) vs Block Scope(ES6)

두 scope의 차이를 더 명확하게 알려면, 자바스크립트가 어떻게 자신이 엑세스 할 수 있는 변수를 어떻게 찾가는 지, 실행하는 원리가 어떠한지 알 필요가 있습니다.

자바스크립트의 작동에 필요한 내부 구조에 대해서 알아봅시다.

실행 문맥의 구성 (Execution Context)

  • 자바스크립트는 실행에 필요한 정보를 저장하는 공간이 있습니다. 바로 실행문맥입니다.

  • 실행문맥은 크게 2가지로 나뉩니다.

    • LexicalEnvironment : 블록의 유효범위 안에 있는 식별자와 그 값이 존재하는 공간. 변수 및 함수를 저장하는 실행공간입니다.(블록 외부도 유효공간입니다. 전역변수를 생각해 보세요.)
    • ThisBinding : 함수를 호출한 객체의 참조가 저장되는 공간. 우리가 잘 아는 this를 말합니다.
  • LexicalEnvironment는 또 2가지로 나뉩니다.

    • EnvironmentRecord : 식별자와 값이 쌍으로 존재합니다.
    • OuterLexicalEnvironment Reference : 현재 블록의 밖을 검색하기 위한 것입니다. 자신을 둘러싸고 있는 LexicalEnvironment를 가리키고 있습니다. 한 단계씩 올라가면서 변수 및 함수를 찾습니다.

정리하자면,

  • LexicalEnvironment // 렉시컬환경 컴포넌트
    • EnvironmentRecord // 환경 레코드
      • DeclarativeEnvironmentRecord //선언적 환경 레코드
      • ObjectEnvironmentRecord //객체 환경 레코드
    • OuterLexicalEnvironment Reference : 외부 엑시컬 환경 참조
  • VariableEnvironment : 변수환경 컴포넌트 (Lexical과 큰 차이X)
  • ThisBinding : null 디스바인딩 컴포넌트

프로그램 실행과 실행 문맥

프로그램이 평가된 다음에 프로그램이 실행됩니다.

  • 먼저 함수의 선언적 환경 레코드가 결정됩니다. 그리고 실행이 됩니다.(함수를 호출하면, 바로 환경레코드를 생성하고, 실행된다. 실행이 끝나면, 실행문맥은 삭제된다. 전역 실행 문맥은 프로그램이 끝날 때 까지 있습니다.) 변수 선언문과 함수의 선언문이 함수의 첫머리로 끌어올려지는 (호이스팅) 이유입니다...

  • 실행하는 함수가 특정 함수 내부에 정의된 중첩함수라면 중첩 함수의 실행 문맥을 새로 만들어서 스택에 push

    • 중첩 함수의 실행 문맥이 외부 함수의 실행 문맥안에서 중첩되지 않습니다!!!
  • 일반적으로 함수의 호출이 끝나면, 환경레코드는 스택에서 삭제됩니다.

  • 중첩된 함수 안에서 바깥 코드에 정의된 변수를 읽거나 쓸 때, 외부 렉시컬 환경 참조를 따라 한 단계씩 렉시컬 환경을 거슬러 올라가 그 변수를 검색합니다.

  • 하지만!!!!!!! 그 함수의 바깥에 위치한 함수의 참조가 해당 환경 레코드에 유지되는 경우에는 호출이 끝나려 스택에서 삭제 하려고 했던 렉시컬 환경 컴포넌트(EC)가 메모리에서 지워지지 않습니다. --> 클로저!(가비지 컬렉터를 생각해보기. 원리가 비슷합니다.)

Function Block(var)

  • 전역 객체와 환경을 생성 한 후, JS 프로그램을 읽습니다.

  • 최상위 레벨에 var문으로 작성한 전역 변수는 전역환경의 환경 레코드의 프로퍼티로 추가됩니다.

  • 최상위 레벨에 선언된 함수는 함수 객체로 생성해서 전역 환경의 환경 레코드의 프로퍼티로 추가됩니다.

  • 일반 함수에서 선언된 var는 그 함수의 코드블록 전체가 스코프 입니다. 그래서, 전역함수에서 생성된 변수는 전역 변수입니다.

  • 변수의 생명주기는 선언, 초기화, 할당 이렇게, 3개의 과정이 있습니다.

  • 그런데, var는 선언과(var i) 초기화가 한번에 이루어집니다.(i=undefined)

  • 그리고 assign(i=3)을 하면 그 때 값을 할당합니다.

전역변수와 전역함수는 프로그램을 평가하는 시점에 전역환경의 환경 레코드에 추가됩니다.

--> 프로그램을 평가하는 시점에 이미 객체 환경 레코드(in 환경레코드)에 추가된 상태이기 때문에, 코드의 어느 위치에 선언해도 전체 프로그램을 참조 할 수 있습니다. 변수 선언과 함수 선언의 끌어올림(Hoisting)이라고 합니다.

만약, var문을 사용하지 않고 변수를 선언하면, 프로그램을 실행 하는 도중에, 디스 바인딩 컴포넌트가 가리키는 환경레코드의 프로퍼티로 추가됩니다. (let, const)

  • 가급적이면 var를 쓰지 않는 것이 좋습니다(ES6이상)

    • 전역변수를 남발하게 됩니다.
    • 변수 선언전에 참조 할 수 있습니다.
    • 유효범위가 넓어서 좋지 않습니다. 가급적 변수의 유효범위는 좁은 게 좋습니다.
    • 중복선언이 가능합니다.
  • 가급적이면, let과 const를 이용합니다.

Block Scope(const, let)

  • Block Scope는 C++, Java에서의 지역변수 개념과 동일합니다.

  • i를 var로 선언하게 되면, 끌어올림이 발생하여 할당(assign)을 하지 않아도 정의가 되어있습니다.

  • 하지만, let과 const같은 Block Scope는 선언을 하고 자신의 유효범위(해당 블록)안에서만 쓸 수 있습니다. 지역변수와 같은 개념입니다.

  • 중복선언이 불가능합니다.

  • 사실 const와 let도 끌어올림이 발생합니다(선언이 된다). 하지만, 해당 변수의 스코프 시작부터 할당 지점까지 접근 할 수 없도록 만들어 두었습니다. 이를 일시적 사각지대라고 합니다

  • const는 c++의 const와 동일합니다. 처음 선언할 때 값을 같이 넣어주어야 합니다. 그리고 값을 수정할 수 없습니다.

  • 가급적이면 const를 쓰는 것이 좋습니다.