자바스크립트의 호이 스팅, 클로저, this에 대한 이해를 하기 위해서는 실행 콘텍스트에 대한 이해가 매우 중요하다. 그럼 실행 콘텍스트란 무엇일까?
실행 컨텍스트?
실행 가능한 코드가 실행되기 위해 필요한 환경이다. 여기서 실행 가능한 코드는 전역 코드나 함수 내 코드를 말한다. 결국 코드를 실행하기 위해 JS 엔진은 코드 실행에 필요한 변수나, 함수, 변수의 유효 범위들을 알고 있어야 하기 때문에 실행 컨텍스트가 필요하게 되는 것이다.
그럼 실행 컨텍스트는 어떻게 이루어져 있을까? 실행컨텍스트는 3가지 객체로 이루어져 있다. 하나씩 알아보자.
첫 번째는 변수 객체이다. 변수 객체는 JS 실행에 필요한 변수, 함수 선언, 함수에 인자로 전달되는 매개변수의 인수를 담는 객체이다. 이때 전역 컨텍스트의 경우에는 전역 객체를 가리키고 함수컨텍스트의 경우 활성 객체를 가리키게 된다.
두 번째는 스코프 체인이다. 일종의 리스트로서 해당컨텍스트가 참조할 수 있는 변수 등의 정보를 갖고 전역 객체 또는 활성 객체이다. 클로저가 발생하는 이유를 설명할 수 있는 중요한 실행 컨텍스트의 구성 요소이다.
마지막은 this이다. this는 일반적으로 메서드를 호출한 객체가 저장되어있는 속성이지만. 함수를 호출하는 방식에 의해서 결정되기도 한다. 나중에 더 자세히 알아보기로 하자.
지금까지 실행 컨텍스트가 먼지? 어떻게 이루어져 있는지 간단하게 알아보았다.
그렇다면 실제로 코드로 확인하기 전에 몇까지 실행 컨텍스트의 특징들을 알아보자.
첫 번째는 실행컨텍스트는 실행컨텍스트 스택에 차례대로 쌓이게 된다. 항상 전역 컨텍스트가 먼저 쌓이게 되고 이후에 함수가 호출될 때마가 새로운 함수컨텍스트가 스택에 쌓이게 된다. 그리고 호출된 함수가 완료되면 컨텍스트에서 사라지게 된다(LIFO 구조)
두 번째는 실행컨텍스트에 쌓이게 되면 3가지 작업이 순서대로 일어난다. 이미 위에서 설명한 것이다. 아래의 3가지 작업은 실행컨텍스트가 쌓일 때마다! 일어나는 것이다.
- 스코프 체인의 생성 및 초기화가 일어난다.
- 변수 객체화가 실행된다. 쉽게 말하면 해당 컨텍스트에 속하는 변수, 함수들이 변수 객체 내에 선언되는 것이다.(이는 또 호이 스팅과 관련이 있다. 참 실행컨텍스트가 중요하다는 생각이 든다.)
- this value를 결정한다.
세 번째는 실행컨텍스트가 스택에 쌓이고 위에서 두 번째 단계에서 말한 3가지 작업들이 실행한다고 실제로 코드가 실행되는 것이 아니다. 코드가 올바르게 규칙에 맞게 실행될 수 있도록 실행환경을 세팅하는 과정 정도로 생각하면 좋을 것 같다.
이제 드디어 코드를 보자!.
var x = 'xxx';
function foo () {
var y = 'yyy';
function bar () {
var z = 'zzz';
console.log(x + y + z);
}
bar();
}
foo();
코드를 작성한 이후에 초기 작업은 항상 공통적으로 일어나게 된다. 컨트롤이 실행 컨텍스트에 진입하기 전에 유일한 전역 객체가 생성된다. 전역 객체가 생성된 이후에 전역 코드로 들어서게 되면 전역 컨텍스트가 생성되고 실행 콘텍스트가 스택에 쌓이게 된다.(아까 말했듯이 변수 객체에서 전역 컨택스트의 변수 객체는 전역 객체가 되고 함수 콘텍스트의 변수 객체는 활성화 객체가 된다.)
자 이제 실행 컨택스트가 스택에 쌓였다. 무슨 일이 일어나게 될까? 바로 위에서 말한 세 가지 작업이 실행되게 되는 것이다.
첫 번째로 스코프 체인의 생성과 초기화가 이루어진다. 전역 콘텍스트의 스코프 체인의 경우에는 전역 객체를 가리키게 된다.
두 번째로는 변수 객체화가 실행이 된다.
자세히 말하자면 실행 콘텍스트의 변수 객체가 전역 객체를 가리키고 전역 객체에 인수, 함수, 변수들을 등록하게 된다. 이때 항상 순서는 함수의 매개변수, 함수, 변수 순으로 설정되는데 이는 함수 호이 스팅과 변수 호이 스팅이 일어나게 된다. 이는 나중에 자세히 알아보자. 자 이제 다시 코드로 돌아와서 확인해 보자
var x = 'xxx';
function foo () {
....
}
foo();
전역에 선언된 변수, 함수인 x, foo()가 있다. 아까 말했듯이 전역 객체(변수 객체)에 함수가 먼저 선언이 된다.
위와 같이 생성된 함수 객체는 scopes라는 속성을 가지게 되는데 이는 현재 실행 콘텍스트의 스코프 체인을 가리키게 된다. 나중에 콘텍스트에 존재하지 않는 변수가 있을 경우 이스 코프 체인을 통해서 해당 변수나 함수를 찾아나 갈 수 있는 것이다.(이는 클로저와 관련이 있다 나중에 알아보기로 하자.)
이제 함수 선언이 끝났기 때문에 변수 선언이 이루어지게 된다. 코드가 실행되어 메모리에 실제 값이 할당되지는 변수 객체화 단계에서는 않고 변수 x를 선언하고 초기화하는 작업만 진행되게 된다. 실제로 코드가 실행되어질 때 변수 x에 실제 값이 할당되게 되는 것이다. 중요한 점은 스코프 체인은 함수가 선언된 위치 기준으로 설정되는 것이다. 나중에 문제를 통해서 확인하자.
세 번째로 this 객체가 설정된다. this 객체는 함수가 호출되는 패턴에 따라 달라지게 되는데 이는 나중에 자세히 알아보자. 참고로 전역 콘텍스트의 변수 객체, 스코프 체인, this는 모두 전역 객체를 가리키게 된다.
이제 전역 코드 공간에 들어와서 실제로 코드가 실행되기 위한 준비는 끝났다(하지만 함수가 호출이 되어 새로운 함수 실행 콘텍스트가 쌓이게 되면 위 과정이 반복된다.).
var x = 'xxx'; => 변수 x 에 값 등록...!
function foo () {
var y = 'yyy';
function bar () {
var z = 'zzz';
console.log(x + y + z);
}
bar();
}
foo(); => 호출!! 함수실행 컨텍스트 생성 스택에 쌓자!!!
자 이제 코드가 실행되고 아까 전역 콘텍스트의 변수 객체인 전역 객체에서 선언되고 초기화만 되었던 변수 x에 실제 값이 할당되게 된다. 아래와 같이 말이다.
다음 코드의 흐름을 보면 함수 foo()를 실행하게 된다. 이때 foo 함수에 대한 실행 콘텍스트가 생성이 되고 스택에 쌓이게 된다. 바로 위에서 말한 스코프 체인 초기화, 변수 객체화, this 설정이 일어나게 된다. 즉 js 엔진은 실제 코드 실행 이전에 함수 내 코드를 수행하기 위한 환경을 세팅하게 된다. 이후에 상세한 과정은 생략하겠다.
함수 호출 이후에 3가지 작업이 일어나게 되고 다음과 같은 모습이 된다.
이제 함수 foo의 실행 콘텍스트 생성 작업이 끝났으니 함수 내 코드를 실행하게 된다. 변수 y를 할당하게 되고 bar()를 만난다. 그럼 다시 함수 bar를 실행하기 위한 실행 콘텍스트가 생성된다.
var x = 'xxx';
function foo () {
var y = 'yyy';
function bar () {
var z = 'zzz';
console.log(x + y + z);
}
bar();
}
foo();
위와 같이 함수 bar 대한 실행 콘텍스트가 생성이 된 이후에 코드가 실행이 된다. console.log(x + y + z); 가 실행이 되는데 현재 함수 bar에 콘텍스트에는 x라는 변수가 없다. 그렇기 때문에 스코프 체인을 통해서 상위 변수 객체(함수면 활성 객체, 전역일 때는 전역 객체) 탐색을 시작한다. 그림과 같이 bar의 스코프 체인은 자신의 함수의 활성화 객체 => 상위 함수의 활성화 객체(foo) => 전역 객체 순으로 연결되어있고 이과정에서 자신에 스코프에는 존재하지 않던 x와 y를 찾아서 접근할 수 있게 된다.
var x = 'xxx';
function foo () {
var y = 'yyy';
function bar () {
var z = 'zzz';
console.log(x + y + z);
}
bar();
}
foo();
=> xxxyyyzzz 출력
그 결과 console.log(x + y + z); 의 결괏값이 출력되고 함수 bar의 호출이 끝났으니 실행 콘텍스트 스택에서 bar의 실행 콘텍스트가 사라지게 되고 foo 도 마찬가지로 사라지게 된다.(스택이니까 LIFO 구조)
문제!
var color = 'black'
function bar (text) {
console.log(text + color)
}
function foo () {
var color = 'white'
bar('color is ')
}
foo()
위와 같은 코드의 출력 결과는 어떻게 될까?
먼저 전역 객체와 전역 콘텍스트가 스택에 쌓이고 스코프 체인 초기화 , 변수 객체화, this value 설정 이 일어난다.(새로운 콘텍스트 생성 시마다 반복)
{ // 전역 컨텍스트
'변수 객체': {
arguments: null,
variable: [ 'color', 'bar(함수)', 'foo(함수)' ]
},
scopeChain: [ '전역 변수 객체' ],
this: window
}
이후 foo 함수가 호출되면서 새로운 함수 콘텍스트가 생성이 되고 스택에 쌓이게 된다.
{ // foo 함수 컨텍스트
'변수 객체': {
arguments: null,
variable: [ 'color' ]
},
scopeChain: [ 'foo 변수 객체', '전역 변수 객체'],
this: window
}
이후 bar 함수가 호출되면서 새로운 콘텍스트가 생성이 되고 스택에 쌓이게 된다. 이때 헷갈릴 수 있는 것이 bar 함수는 foo 함수 내부에서 호출이 되었지만 bar 함수의 스코프 체인을 확인해보면 foo 함수는 없다. 이유는 스코프 체인은 함수의 실행이 아닌 선언 당시에 스코프 체인의 값이 결정되기 때문이다. bar 함수는 전역에 선언되었기 때문에 스코프 체인은 자신의 활성화 객체와, 전역 객체를 가리키게 된다.
{ // bar 함수 컨텍스트
'변수 객체': {
arguments: [{ text: 'color is' }],
variable: null
},
scopeChain: [ 'bar 변수 객체', '전역 변수 객체'],
this: window
}
이제 bar 함수의 내부가 실행이 되는데 위에서 확인한 거와 같이 bar의 변수 객체에서 arguments(함수 인자) text 값을 찾을 수 있다. 이후 color 값을 찾기 위해 자신의 콘텍스트의 변수 객체를 확인하지만 없기 때문에 다음 스코프 체인인 전역 변수 객체를 확인하게 된다. 전역 변수 객체에는 color 이 존재하기 때문에 참조를 하여 최종 결괏값인 color is black 이 출력되게 된다.
function bar (text) {
console.log(text + color) => color is black
}
정리!
실행 콘텍스트는 실행 가능한 코드가 실행되기 위해 필요한 환경이다. 실행 컨텍스 생성은 코드를 실행하기 전에 필요한 환경을 세팅하기 위해 필요한 작업
함수가 호출될 때마다 실행 콘텍스트가 스택에 쌓이게 되고 새로운 콘텍스트가 생성된다.
실행 콘텍스트는 변수 객체, 스코프 체인, this value로 이루어져 있고. 실행 콘텍스트가 생성되어 스택에 쌓일 때마다 스코프 체인 초기화 => 변수 객체화 => this value 설정 순으로 일어난다.
스코프 체인은 클로저, 변수 객체화의 변수, 함수의 선언과 초기화는 호이 스팅과 관련이 있다.
참조
https://poiemaweb.com/js-execution-context
https://www.zerocho.com/category/JavaScript/post/5741d96d094da4986bc950a0
https://jinminkim-50502.medium.com/execution-context-%EC%8B%A4%ED%96%89-%EC%BB%A8%ED%85%8D%EC%8A%A4%ED%8A%B8-4d6d082c83ff
'JS' 카테고리의 다른 글
호이스팅 (0) | 2020.12.28 |
---|---|
Node JS 동작원리 (1) | 2020.12.28 |