두 scope의 차이를 더 명확하게 알려면, 자바스크립트가 어떻게 자신이 엑세스 할 수 있는 변수를 어떻게 찾가는 지, 실행하는 원리가 어떠한지 알 필요가 있습니다.
자바스크립트의 작동에 필요한 내부 구조에 대해서 알아봅시다.
실행 문맥의 구성 (Execution Context)
자바스크립트는 실행에 필요한 정보를 저장하는 공간이 있습니다. 바로 실행문맥입니다.
실행문맥은 크게 2가지로 나뉩니다.
- LexicalEnvironment : 블록의 유효범위 안에 있는 식별자와 그 값이 존재하는 공간. 변수 및 함수를 저장하는 실행공간입니다.(블록 외부도 유효공간입니다. 전역변수를 생각해 보세요.)
- ThisBinding : 함수를 호출한 객체의 참조가 저장되는 공간. 우리가 잘 아는 this를 말합니다.
LexicalEnvironment는 또 2가지로 나뉩니다.
- EnvironmentRecord : 식별자와 값이 쌍으로 존재합니다.
- OuterLexicalEnvironment Reference : 현재 블록의 밖을 검색하기 위한 것입니다. 자신을 둘러싸고 있는 LexicalEnvironment를 가리키고 있습니다. 한 단계씩 올라가면서 변수 및 함수를 찾습니다.
정리하자면,
- LexicalEnvironment // 렉시컬환경 컴포넌트
- EnvironmentRecord // 환경 레코드
- DeclarativeEnvironmentRecord //선언적 환경 레코드
- ObjectEnvironmentRecord //객체 환경 레코드
- OuterLexicalEnvironment Reference : 외부 엑시컬 환경 참조
- EnvironmentRecord // 환경 레코드
- 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를 쓰는 것이 좋습니다.
'Backend > Javascript' 카테고리의 다른 글
Static type language vs Dynamic type language (0) | 2019.04.10 |
---|---|
자바스크립트와 다른언어의 차이점 (0) | 2019.03.30 |
자바스크립트의 프로토타입 (0) | 2019.03.30 |