실행 컨텍스트란 자바스크립트 코드가 실행되고 있는 범위에 대한 정보를 저장하고 있는 객체이다. 자바스크립트 엔진은 스크립트 혹은 함수를 실행할 때 새로운 컨텍스트를 만들고 콜 스택에 쌓는다.
function printName(){
return 'Choi Dong Wook'
}
function findName(){
return printName()
}
function sayMyName(){
return findName()
}
sayMyName()
다음과 같은 코드가 있다고 가정해보자. 자바스크립트 엔진이 sayMyName()
을 보는 즉시 실행 컨텍스트라는 것을 생성하고 콜 스택에 push한다. sayMyName
을 수행하는 과정에서 필요헌 findName()
와 printName()
도 콜 스택에 push하게 된다.
그림에서 보면 기본적으로 Global()
이라는 함수가 실행 컨텍스트중 가장 밑바닥에 존재하는 것을 확인할 수 있다. 이는 가장 기본이 되는 실행 컨텍스트로 Global Execution Context(전역 컨텍스트)라고 한다.
전역 컨텍스트는 자바스크립트 엔진이 script
태그를 처음 만나는 순간 생성된다. 전역 컨텍스트는 생성되는 순간 global Object와 this를 만들고 전역 컨텍스트의 this를 global Object로 할당한다. 또한 추후에 생성되는 변수 , 함수는 global Object에 저장된다.
위 사진을 보면 this
객체와 window(Global Object)
객체가 동일하다.
또한 추가로 var a = 'Teddy'
라는 변수를 선언했더니 window 객체에 저장된 것을 확인할 수 있다.
전역 컨텍스트를 포함한 실행 컨텍스트에는 두 가지 단계가 있는데 첫 번째는 바로 Creation Phase이고 두번째는 Execution Phase이다.
일단 지금은 Creation Phase는 변수나 함수들을 메모리에 할당하는 과정이고 Execution Phase는 실제로 코드가 구동되는 과정이라고만 알아두고 추후에 자세하게 알아보자.
렉시컬 환경이란 어떠한 변수나 함수가 선언된 환경, 즉 특정 코드가 어디에서 작성되었는지라고 이해하면 될 것 같다.
말로 설명하면 어려우니 바로 예시를 들어서 이해하도록 해보자
function printName(){
return 'Andrei Neagoie'
}
function findName(){
function a(){
}
return printName()
}
function sayMyName(){
return findName()
}
sayMyName()
어디에서 선언되었는지를 중점으로 함수 sayMyName
, printName
, findName
들을 살펴보자.
sayMyName
은 전역 컨텍스트 에서 선언되었다. 그러므로 sayMyName
의 렉시컬 환경은 global 이다.
마찬가지로 findName
이나 printName
두 함수모두 전역 컨텍스트 에서 선언되었으므로 두 함수의 렉시컬 환경 또한 global 이다.
a 의 경우에는 findName
함수 내부에서 선언되었으므로 a 의 렉시컬 스코프는 findName
이다.
a를 제외한 함수 모두 렉시컬 환경이 global 이므로 어떠한 함수를 바로 호출해도 정상적으로 코드가 실행되는 것을 확인할 수 있다. 또한 실행 컨텍스트에서 변수나 함수가 추가될 경우 global 객체에 저장된다고 하였으므로 window.함수
의 방식으로도 호출이 가능하다.
a 의 경우는 렉시컬 스코프가 findName()이므로 global 객체에 저장되지 않으므로 호출이 불가능하다.
이러한 렉시컬 스코프를 왜 알아야 하는것일까?
만약 내가 사용하고자 하는 변수나 함수가 어떤 렉시컬 환경에 속해있는지에 따라 이용 가능한 변수가 달라지기 때문이다. 결론적으로 어떤 변수나 함수는 어디에서 호출했는지가 아닌 어디에서 선언했는지 즉, 렉시컬 환경이 어디인지에 따라 결정된다.
호이스팅이란 변수나 함수의 선언부분을 모두 유효 범위의 최상단으로 끌어 올리는것을 의미한다.
이 때 실제로 코드가 끌어올려지는 것은 아니며 자바스크립트 엔진 내의 Parser 가 내부적으로 끌어올려서 처리한다.
이건 또 무슨말인지 알아보자.
console.log('1-------')
console.log(teddy);
console.log(sing());
var teddy = 'bear';
function sing(){
console.log('ohhh la la la')
}
위와 같은 코드를 실행하면 다음과 같은 결과가 나온다.
1-------
과 undefined
는 그렇다 치는데 ohhh la la la
는 선언도 되기 전에 호출했음에도 불구하고 제대로 된 결과값이 나오는 것을 확인할 수 있다. ( 마지막 줄의 undefined는 sing의 return 값이 없기 때문에 나온 결과이다.)
어떤일이 일어났길래 위와 같은 결과가 나왔을까?
먼저 위에서 언급했듯 전역 컨텍스트는 Creatrion Phase 와 Execution Phase로 나뉜다고 하였다. 자바스크립트 엔진은 전역 컨텍스트의 Creation Phase때 변수나 함수에 대한 메모리를 먼저 할당해 놓은 다음(호이스팅) Execution Phase에서 해당 선언 부분을 만나면 값과 매칭을 하는 방식으로 실행된다.
이때 변수는 부분적으로 호이스팅이 발생하고 , 함수의 경우는 전부 호이스팅이 발생하기 때문에 위와같은 결과가 나온 것이다. 이를 코드로 나타낸다면 다음과 같을 것이다. ( 실제로 코드가 최상단으로 이동하는 것이 아니다. )
console.log('1-------')
//hoisiting
var teddy = undefined;
function sing(){
console.log('ohhh la la la')
}
console.log(teddy);
console.log(sing());
teddy = 'bear';
console.log(a); // undefined
console.log(b); // ReferenceError
console.log(c); // ReferenceError
var a = 'a';
let b = 'b';
const c = 'c';
앞에서 변수는 일부분만 호이스팅 된다고 하였지만 let
과 const
키워드를 사용하여 변수를 선언할 경우 ReferenceError 가 발생한다.
(후술하겠지만 호이스팅이 되지 않는 것이 아니다.)
변수는 메모리에 저장될 때 3단계의 과정을 통해서 생성된다.
먼저 var
키워드의 경우 선언 단계와 초기화 단계가 동시에 이루어진다.
let
과 const
의 경우에는 선언 단계와 초기화 단계가 분리되어 진행된다. 호이스팅이 일어난 후 선언 단계가 이루어지지만 초기화 단계는 직접 변수를 선언한 줄에 접근했을때 이루어 진다.
참고로 let
과 const
키워드가 호이스팅이 되지 않는것이 아니다 아래와 같은 예시를 보자.
설명하기에 앞서 let
과 const
는 블록 레벨 스코프를 가진다. 즉 {} 내부에 변수를 선언하면 해당 블록 내부에서만 생명주기를 유지한다. 반대로 var
는 함수 레벨 스코프를 가지므로 외부에서 접근할 수 있다.
만약 let
키워드에 호이스팅을 지원하지 않는다면 콘솔에 teddy라는 결과값이 찍혀야 하겠지만 호이스팅의 결과로 인해서 ReferenceError 가 발생하는 것을 확인할 수 있다.