Published on

실행 컨텍스트(Execution Context)

Authors
  • avatar
    Name
    Inhwan Cho
    Twitter

실행 컨텍스트(Execution Context)에서 Context란 자바스크립트 코드가 실행되기 위한 환경 또는 범위를 의미합니다.

모든 코드는 실행 컨텍스트 내에서 실행되며, 실행 컨텍스트는 글로벌 컨텍스트, 함수 컨텍스트, eval 컨텍스트 등 여러 종류가 있습니다.

여기서는 가장 자주 접하게 되는 글로벌 컨텍스트함수 컨텍스트에 대해 알아보겠습니다.

글로벌 컨텍스트

가장 바깥쪽 범위의 코드를 실행하기 위한 기본 레벨의 컨텍스트입니다.

JavaScript 코드가 실행되면, 가장 먼저 글로벌 실행 컨텍스트가 생성됩니다.

이는 전역 변수와 함수를 포함하며, 프로그램 전체에서 단 하나만 존재합니다.

정리하자면,

  • 자바스크립트 파일이 처음 실행될 때, 기본적으로 만들어지는 컨텍스트입니다.
  • window 객체(브라우저 환경) 또는 global 객체(Node.js 환경)를 전역 객체로 가집니다.
  • 변수나 함수 선언은 글로벌 컨텍스트에 등록됩니다.

함수 컨텍스트

  • 함수가 호출될 때마다 생성되는 컨텍스트입니다.
  • 해당 함수의 지역 변수, 매개변수, this 값 등을 포함합니다.

두 컨텍스트를 비교해봅시다.

구분글로벌 컨텍스트함수 컨텍스트
생성 시점자바스크립트 파일 실행 시 최초 생성함수가 호출될 때마다 생성
포함하는 내용전역 객체, 전역 변수와 함수 선언지역 변수, 매개변수, this 값

실행 컨텍스트의 생성 과정

실행 컨텍스트의 생성에는 크게 두 단계, 즉 생성 단계와 실행 단계가 있습니다.

생성 단계에서는 환경 레코드(Environment Record)와 호이스팅, this 바인딩이 일어납니다.

실행 단계에서는 코드가 실제로 실행되면서 변수에 값이 할당되거나 함수가 호출됩니다.

여기서 용어를 한 번 정리해보겠습니다.


환경 레코드

환경 레코드는 실행 컨텍스트에 포함된 식별자(변수, 함수 선언 등)와 그 식별자에 바인딩된 값을 저장하는 구조입니다.

이는 실행 컨텍스트 내에서 선언된 모든 식별자의 정보를 관리하며, 코드에서 특정 식별자를 참조할 때 JavaScript 엔진이 해당 식별자의 값을 찾기 위해 환경 레코드를 조회합니다.

호이스팅(Hoisting)

호이스팅은 변수나 함수 선언이 그들이 포함된 실행 컨텍스트의 최상단으로 끌어올려진 것처럼 동작하는 JavaScript의 동작 방식을 설명하는 개념입니다.

실제로 코드가 재배치되는 것은 아니지만, JavaScript 엔진은 실행 컨텍스트가 생성될 때 변수와 함수 선언을 먼저 읽어서 환경 레코드에 저장합니다.

이로 인해 코드 내 어디에서든 변수나 함수를 사용할 수 있게 됩니다.

this 바인딩(this Binding)

this 바인딩은 실행 컨텍스트가 생성될 때 this의 값이 결정되는 과정입니다.

this의 값은 함수가 어떻게 호출되었는지에 따라 달라집니다.


예시로 이해하기

다음은 간단한 코드 예시와 함께 실행 컨텍스트의 동작을 설명합니다.

html
<script>
var number = 10; // 글로벌 컨텍스트에 포함됩니다.

function showNumber() {
  console.log(number); // 함수 컨텍스트에 포함됩니다.
}

showNumber();
</script>

위 예시에서, var number = 10; 은 글로벌 컨텍스트에 포함됩니다. 이후 showNumber() 함수가 호출될 때, 새로운 함수 컨텍스트가 생성됩니다.

showNumber 함수 내에서 console.log(number);가 실행될 때, 지역 변수가 없으므로 상위 컨텍스트인 글로벌 컨텍스트에서 number 변수를 찾아 값을 출력합니다.


아래의 개념들도 실행 컨텍스트와 관련된 용어인데 살펴보겠습니다.

스코프(Scope), 클로저(Closure)

스코프는 변수나 함수가 액세스할 수 있는 범위를 나타냅니다.

JavaScript에는 크게 글로벌 스코프(Global Scope)지역 스코프(Local Scope)가 있습니다.

  • 글로벌 스코프(Global Scope): 코드의 어느 곳에서나 접근할 수 있는 변수나 함수가 정의됩니다.
  • 지역 스코프(Local Scope): 함수 내에서만 접근 가능한 변수나 함수가 정의됩니다.

지역 스코프는 또한 블록 스코프(Block Scope)를 포함할 수 있는데, 이는 letconst로 선언된 변수가 속하는 스코프입니다.

scopeExample.js
let globalVar = "전역 변수";

function exampleFunction() {
    let localVar = "지역 변수";
    console.log(globalVar); // 전역 변수에 접근할 수 있습니다.
    console.log(localVar); // 지역 변수에 접근할 수 있습니다.
}

console.log(globalVar); // 전역 변수에 접근할 수 있습니다.
console.log(localVar); // 오류! 지역 변수에 접근할 수 없습니다.

클로저(Closure)

클로저는 함수와 그 함수가 선언된 스코프의 환경을 함께 참조하는 표현입니다. 클로저를 이용하면 지역 변수를 함수가 실행된 이후에도 유지할 수 있습니다.

closureExample.js
function outerFunction() {
    let outerVar = '외부 변수';

    function innerFunction() {
        console.log(outerVar); // 외부 변수에 접근할 수 있습니다.
    }

    return innerFunction;
}

const newFunction = outerFunction();
newFunction(); // '외부 변수'

네이티브 객체(Native Objects)와 호스트 객체(Host Objects)

  • 네이티브 객체(Native Objects): JavaScript 언어 사양에 정의된 객체로, 개발자가 JavaScript 코드를 통해 접근할 수 있습니다.

예를 들어 String, Number, Array, Object 등이 있습니다.

  • 호스트 객체(Host Objects): JavaScript가 실행되는 호스트 환경(브라우저, Node.js 등)에서 제공하는 객체로, 호스트 환경에 따라 다를 수 있습니다.

예를 들어 브라우저 환경에서는 window, document 등이 있습니다.


var vs let vs const

개념에 대해 설명해보았으니 실제로 사용하는 변수들을 선언할때 사용되는 키워드를 살펴보겠습니다.

JavaScript에서 var, let, const는 변수를 선언할 때 사용하는 키워드입니다.

이들의 차이점은 스코프(scope), 호이스팅(hoisting), 재할당(reassignment)에서 차이가 있습니다.

var

var 키워드는 ES6 이전에 JavaScript에서 변수를 선언하기 위해 사용되었습니다. var로 선언된 변수는 다음과 같은 특징을 가집니다.

  • 함수 레벨 스코프(Function-level scope): 함수 내에서 선언된 변수는 함수 내 어디에서든 접근 가능합니다.
  • 호이스팅(Hoisting): 변수를 선언하기 전에도 그 변수를 참조할 수 있습니다. 단, 초기화 되기 전까진 undefined 값이 할당됩니다.

예시

index.js
// 어디서든 var 호출 가능. 단, 초기화 전까진 undefined
console.log(beforeVar) // undefined
var beforeVar = "저는 호이스팅 되었습니다.";

function varScopeExample() {
  if (true) {
    var test = "함수 스코프 안에서 저는 접근 가능합니다.";
  }
  console.log(test); // 함수 스코프 안에서 저는 접근 가능합니다.
}

// 함수 내 선언된 것은 호출 불가능.
// ReferenceError: y is not defined
// console.log(test) 

let

let 키워드는 ES6에서 도입되었으며, 대부분의 경우 var를 대체하여 사용됩니다. let의 주요 특징은 다음과 같습니다.

  • 블록 레벨 스코프(Block-level scope): 중괄호 {}로 묶인 블록 내에서만 변수가 유효합니다.
  • 변수 재선언 불가: 동일한 스코프 내에서 변수를 다시 선언할 수 없습니다.

예시

index.js
console.log(greeting) // ReferenceError: Cannot access 'greeting' before initialization
let greeting = "안녕하세요";
greeting = "반갑습니다"; // 재할당 가능

// 다시 선언 불가능 
// SyntaxError: Identifier 'greeting' has already been declared
// let greeting = "다시 선언"; 

if (true) {
  let blockScope = "저는 블록 스코프 안에서만 유효합니다.";
  console.log(blockScope); // 저는 블록 스코프 안에서만 유효합니다.
}

// 함수 내에서 선언된 let을 외부에서 호출 불가능.
// ReferenceError: blockScope is not defined
// console.log(blockScope); 

const

const도 ES6에서 함께 도입된 키워드입니다. const는 다음 특성을 가집니다.

  • 블록 레벨 스코프(Block-level scope): let과 동일하게 중괄호 {}로 묶인 영역 내에서만 유효합니다.
  • 재할당 불가: 한 번 할당된 값은 변경할 수 없습니다. (단, 객체의 속성은 변경 가능)

예시

index.js
const greeting = "안녕하세요";
// 값 변경 불가능.
// TypeError: Assignment to constant variable.
// greeting = "반갑습니다"; 

const obj = { key: "값" };
obj.key = "새로운 값"; // 가능
// obj = {}; // TypeError: Assignment to constant variable.

if (true) {
  const blockScope = "저는 블록 스코프 안에서만 유효합니다.";
  console.log(blockScope); // 저는 블록 스코프 안에서만 유효합니다.
}

비교

아래는 var, let, const의 주요 차이점을 비교한 표입니다.

특성varletconst
스코프함수 레벨블록 레벨블록 레벨
재선언가능불가능불가능
재할당가능가능불가능