Mina's World, https://mingcoder.me/2020/02/28/Programming/JavaScript/execute-context/
PoiemaWeb, https://poiemaweb.com/es6-block-scope
PoiemaWeb, https://poiemaweb.com/js-function
참고 사이트에 내용을 개인적으로 복습하기 편하도록 재구성한 글입니다.
자세한 설명은 참고 사이트를 살펴보시기 바랍니다.
자바스크립트 언어를 이해하는데 가장 중요하고 기본적인 컨셉은 실행 컨텍스트를 이해하는 것입니다. 이를 잘 이해한다면 호이스팅, 스코프 체인, 클로저를 이해하기 쉬울 것입니다.
실행 컨텍스트라는 것이 무엇인지 이해하기 위해 소프트웨어를 어떻게 만드는 지 생각해봅시다. 소프트웨어를 작성하는 기본적인 전략은 코드를 여러 조각으로 나누는 것입니다. 코드 조각들은 functions, modules, packages 등으로 나누어집니다. 이렇게 분리하는 이유는 소프트웨어의 복잡성을 줄이고 관리하기 쉽게 하기 위함입니다.
소프트웨어를 작성하는 입장을 이해하고 코드를 해석하는 자바스크립트 엔진 관점에서 생각해봅시다. 자바스크립트 엔진은 코드 해석의 복잡성을 관리하기 위해서 소프트웨어를 작성하는 기본 전략처럼 코드를 여러 조각으로 나눕니다. 이렇게 나누어진 조각들을 실행 컨텍스트라고 부릅니다.
실행 컨텍스트의 종류는 두 가지입니다.
글로벌 실행 컨텍스트
함수 실행 컨텍스트
실행 컨텍스트는 두 단계로 실행됩니다.
생성 단계
실행 단계
자바스크립트 엔진이 코드를 해석 시 가장 먼저 생성되는 실행 컨텍스트입니다.
글로벌 실행 컨텍스트는 기본적으로 두 가지로 구성됩니다.
global object
this
아무런 코드를 작성하지 않아도 글로벌 실행 컨텍스트는 위 두 가지 구성 요소를 포함합니다.
현재 단계는 자바스크립트 엔진이 코드를 한 줄씩 읽고 실행하지 않습니다.
자바스크립트 엔진은 다음과 같이 동작합니다.
global object
생성
this
생성
var
변수를 글로벌 실행 컨텍스트에 선언하고 메모리 공간을 확보하여 undefined
로 초기화
선언식 함수를 선언하고 메모리 공간을 확보하여 함수를 할당
현재 단계에 접어들면, 자바스크립트 엔진은 코드를 한 줄씩 실행
코드를 한 줄씩 실행하면서 var
변수에 값을 할당
함수 실행 컨텍스트는 함수가 호출될 때마다 생성되는 실행 컨텍스트입니다.
함수 실행 컨텍스트는 기본적으로 두 가지로 구성됩니다.
arguments object
this
함수 내부에 아무런 코드를 작성하지 않아도 함수 실행 컨텍스트는 위 두 가지 구성 요소를 포함합니다.
함수가 호출되면 함수 실행 컨텍스트를 생성합니다.
자바스크립트 엔진은 다음과 같이 동작합니다.
arguments object
생성
this
생성
var
변수를 함수 실행 컨텍스트에 선언하고 메모리 공간을 확보하여 undefined
로 초기화
선언식 함수를 선언하고 메모리 공간을 확보하여 함수를 할당
글로벌 실행 컨텍스트와 차이점은 global object
가 아닌 arguments object
를 생성한다는 것이고 나머지는 동일합니다.
실행 단계에 접어들면, 자바스크립트 엔진은 함수 내부에 코드를 한 줄씩 실행
코드를 한 줄씩 실행하면서 var
변수에 값을 할당
var
변수와 선언식 함수가 없기에 메모리 할당 과정을 거치지 않습니다.getURL()
함수는 handle
을 인자로 넘겨 받고, 함수 내부에 var
변수를 가집니다.
생성 단계에서 인자로 전달된 handle
을 arguments object
에 추가하고, 함수 실행 컨텍스트 내부에 지역 변수로 추가됩니다.
지역 변수 twitterURL
은 생성 단계에서 메모리 공간을 확보하고 undefined
로 초기화됩니다.
이후 실행 단계에서 twitterURL
은 값을 할당받습니다.
자바스크립트 엔진은 싱글 스레드이므로 함수가 호출될 때마다 Call Stack에 함수 실행 컨텍스트를 생성하여 차곡 차곡 쌓습니다.
함수가 호출되면 Call Stack에 쌓였다가 실행을 마치면 Call Stack에서 제거됩니다.
호이스팅(Hoisting)은 실행 컨텍스트의 생성 단계에서 변수와 함수가 동작하는 방식을 의미합니다.
변수의 호이스팅 설명 이전에, 변수의 생성 과정은 아래와 같이 3단계로 구성됩니다.
선언 단계(Declaration phase)
변수를 실행 컨텍스트에 등록한다.
초기화 단계(Initialization phase)
변수를 위한 메모리 공간을 확보합니다. 이 단계에서 변수는undefined
로 초기화됩니다.
할당 단계(Assignment phase)
undefined
로 초기화된 변수에 실제 값을 할당합니다.
실행 컨텍스트의 생성 단계에서 아래와 같이 var
를 명시한 이유는 let
과 const
와 호이스팅 방식이 다르기 때문입니다.
var
변수를 함수 실행 컨텍스트에 선언하고 메모리 공간을 확보하여 undefined
로 초기화var
는 실행 컨텍스트의 생성 단계에서 선언 단계와 초기화 단계가 동시에 일어나고, 실행 단계에서 할당 단계가 실행됩니다.
console.log(foo); // undefined
var foo = 1;
console.log(foo); // 1
let
과 const
는 실행 컨텍스트의 생성 단계에서 선언 단계만 일어나고, 실행 단계에서 초기화 단계와 할당 단계가 실행됩니다.
console.log(foo); // ReferenceError: foo is not defined
let foo = 1;
console.log(foo); // 1
함수를 생성하는 방법은 대표적으로 선언식 함수와 표현식 함수가 있습니다.
// 선언식 함수
function foo() {...};
// 표현식 함수
var foo = function() {...};
실행 컨텍스트의 생성 단계에서 아래와 같이 선언식 함수를 명시한 이유는 표현식 함수와 호이스팅 방식이 다르기 때문입니다.
선언식 함수는 실행 컨텍스트의 생성 단계에서 선언과 동시에 메모리 공간을 확보하여 함수를 할당합니다.
foo(); // 함수 호출 가능
function foo() {...};
표현식 함수는 실행 컨텍스트의 생성 단계에서 선언만 이루어지고, 실행 단계에서 메모리 공간을 확보하여 함수를 할당합니다.
foo(); // TypeError: foo is not a function
var foo = function() {...};
스코프 체인(Scope Chain)이란 현재 실행 컨텍스트에 필요한 변수가 없는 경우, 자바스크립트 엔진이 부모 실행 컨텍스트를 하나씩 차례대로 확인하는 프로세스를 의미합니다. MDN에서는 스코프를 현재 실행 컨텍스트라고 정의합니다.
아래 코드의 동작을 한번 살펴봅시다.
var name = 'Tyler';
function logName() {
console.log(name)
};
logName(); // Tyler
logName()
함수의 실행 컨텍스트에는 name
변수가 없으므로 not defined
에러가 발생할 것이라고 예상할 수 있습니다. 하지만 자바스크립트 엔진은 함수 실행 컨텍스트에서 지역 변수를 찾지 못하면 가장 가까운 부모 실행 컨텍스트로 이동하여 해당 변수를 찾게됩니다. 이는 글로벌 실행 컨텍스트에 도달할 때까지 거슬러 올라갑니다. 위의 코드에서는 글로벌 실행 컨텍스트에 name
변수를 발견하여 해당 값을 출력할 수 있습니다.
함수 내부에서 생성된 변수는 내부에서만 접근 가능하고 함수 실행 컨텍스트가 Call Stack에서 제거되면 그 변수에 접근할 수 없다고 알고있습니다. 하지만 예외적인 경우가 있는데, 이는 함수 안에 중첩 함수가 존재하는 경우입니다.
이 경우에는 부모 함수의 실행 컨텍스트가 실행 스택에서 제거되었더라도 자식 함수는 부모 함수의 실행 컨텍스트에 여전히 접근 가능합니다.
위의 과정을 확인해보면, makeAdder()
함수의 실행 컨텍스트가 실행 스택에서 제거되고 난 후 Closure Scope를 생성하는 것을 확인할 수 있습니다.
자바스크립트는 makeAdder()
함수 안에 inner()
함수가 존재하고 inner()
함수가 실행되기 전에 반환될 때 makeAdder()
함수의 실행 컨텍스트 환경을 가지게 됩니다.
이런 특징 때문에 makeAdder()
함수가 실행 스택에서 제거되어도 inner()
함수는 생성된 Closure Scope로 변수에 접근할 수 있습니다.