코어자바스크립트 - 실행 컨텍스트

2021-08-122021-08-12
  • Javascript
  • Book

📖 이 글은 코어자바스크립트를 읽고 책을 바탕으로 이해한 내용을 작성한 글입니다.

실행 컨텍스트는 자바스크립트를 이해하기 위해 가장 중요한 개념이다. 실행 컨텍스트를 이해함으로써 해소할 수 있는 궁금증은 다음과 같다.

  • 호이스팅이란?
  • 전역변수와 지역변수란?
  • 스코프란?
  • let, const var의 차이?
  • 함수 선언식과 함수 표현식의 차이?

그리고 실행 컨텍스트를 이해함으로써 더 잘 이해할 수 있는 개념들은 다음과 같은 것들이 있다.

  • this
  • 클로져
  • 화살표 함수

선행 개념 : 콜스택

실행 컨텍스트를 이해하기 위해 알아야 하는 선행 개념 중 하나는 콜스택이다.
스택은 밑이 막혀있는 컵에 차례대로 데이터를 쌓는 구조이며, 먼저 들어간 것이 가장 밑에 쌓여 가장 마지막에 꺼내지는 (실행되는) 자료 구조이다.

다음의 코드를 콜스택과 실행 컨텍스트를 활용해 설명할 수 있다. 만약 다음의 코드를 설명하지 못한다면 🔻실행 컨텍스트에 대해 먼저 공부해보고 아래의 코드를 설명해보자.

// basic
function three() {
  console.log('hi');
}

function two() {
  three();
}

function one() {
  two();
}

function zero() {
  one();
}

zero();

위의 코드에서 콜스택에는 zero, one, two, three가 차례대로 담긴다. 가장 위에 쌓인 것이 three이기 때문에 three를 실행하고, 따라서 console에 ‘hi’가 찍힌다. 이로써 three의 실행이 완료되었으므로 콜스택에서 삭제되고 다시 two 함수로 돌아오지만 three를 호출하는 것 외에 다른 실행할 것이 없으므로 two도 종료된다. one, zero도 마찬가지이다.

function three() {
  console.log('hi');
}

function two() {
  three();
}

function one() {
  two();
}

function zero() {
  one();
  throw Error('error'); // 에러가 발생하는 시점은?
}

zero();

이번에는 Error 객체를 만들어 던져보겠다. 콜스택에는 zero, one, two, three가 쌓일 것이다. zero, one, throw Error 순으로 쌓이지 않는 것은 one을 호출함으로써 one안의 실행 컨텍스트가 수집되어 콜스택에 쌓이기 때문이다. 콜스택 최상위에 one이 쌓였으므로 zero의 실행을 중단하고 one을 실행한다.

따라서 throw Error는 그 위의 콜스택들이 전부 사라지고 난 뒤에 실행된다. 콘솔에 ‘hi’가 먼저 찍히고, 첫번째 예제에서처럼 three, two, one이 차례대로 콜스택에서 사라진다. 그리고 zero 함수로 돌아오는데 one 함수를 호출하는 것까지는 했고 남은 것이 throw Error이므로 그때서야 ‘error’라는 에러가 발생하게 된다.

function three() {
  console.log('hi');
  throw Error('error'); // 에러가 발생하는 시점은?
}

function two() {
  three();
  console.log('이 콘솔로그는 실행될까?');
}

function one() {
  two();
}

function zero() {
  one();
}

zero();

이 세 번째 예제는 두 번째 예제와 비슷하지만, 최상위에 놓이게 되는 three에서 에러가 던져졌으므로 two, one, zero에서 일시 중단되었던 이후의 코드는 실행되지 않는다. 즉 two함수의 console.log('이 콘솔로그는 실행될까?')는 실행되지 않는다.

실행 컨텍스트란?

컨텍스트?

먼저 컨텍스트란 무슨 말일까?
CS에서 컨텍스트란 작업이 중단되고 나중에 동일한 지점에서 계속될 수 있도록 저장해야하는 작업에 사용되는 최소한의 데이터 집합이라고 한다.

이것으로 위의 콜스택 예제에서처럼 특정 함수 a를 실행하다가 다른 함수 b 호출을 만나 b를 실행하고 다시 a의 실행 중단된 지점으로 돌아와 계속하는 것을 설명할 수 있다.

실행 컨텍스트?

그럼 자바스크립트에서의 실행 컨텍스트란?
실행할 코드에 제공할 환경 정보들을 모아놓은 객체를 의미한다. 바꿔 말하면 그 코드를 실행하기 위해 필요한 정보들,이 될 수 있겠다.

그 객체는 일반적으로 ‘컨텍스트’라는 말로 많이 표현하며, 컨텍스트는 구성된 후 콜스택에 쌓여진다. 그리고 자바스크립트는 가장 위에 쌓인 컨텍스트와 관련있는 코드를 실행한다.

실행 컨텍스트를 구성할 수 있는 방법은 세 가지이다.

  • 전역 공간 : 자동 생성된다
  • eval() 함수 : 보안상의 이유로 사용을 지양한다.
  • 함수 : 우리는 이 방법으로 가장 많이 실행 컨텍스트를 생성한다.

먼저 알아둬야 하는 것

  • ‘식별자가 선언된다’의 의미
    어떤 식별자가 선언된다는 것은 콜스택 상에서 어떤 실행 컨텍스트가 활성화된 상태를 의미한다는 것이다.

  • 식별자의 의미
    식별자란 이전 글에서도 다루기도 했지만 2장에서는 매개변수의 이름, 함수, 변수명을 의미한다.

  • 실행 컨텍스트 활성화의 의미
    실행 컨텍스트가 활성화된다는 것은(=생성된다는 것은) 관련 코드를 실행하는데 필요한 환경 정보들을 수집해서 실행 컨텍스트에 저장한다는 것을 의미한다.

종합해보면 매개변수의 이름, 함수, 변수명 등이 선언되면 자바스크립트가 관련 코드를 실행하는데 필요한 환경 정보들을 수집해 실행 컨텍스트에 저장한다,로 풀어 말할 수 있겠다.

전역 공간에서의 활용을 예시로 들어보자.
전역 공간에서 선언된 값들은 별도의 실행 명령 없이도 브라우저에서 자동으로 실행한다. 즉, js파일이 로드되고 열리는 순간 실행되는 것으로, 전역 컨텍스트 또한 자동으로 활성화된다. 당연히, 콜스택에도 담긴다. 전역 공간에서의 실행 컨텍스트는 자바스크립트가 별도로 제공하는 전역 객체를 활용하는데, 보통 window이며 node에서는 global이다. 만약 strict mode라면 undefined로 표시된다.

환경정보?

그럼 실행 컨텍스트에 저장되는 환경정보란 무엇일까? 환경 정보란 다음 세 가지를 의미한다.

  • Variable Environment : 현재 컨텍스트 내 식별자들 정보 + 외부환경 정보를 의미한다. 선언 시점의 Lexical Environment와 같다. 변경 사항은 반영되지 않는다.

  • Lexical Environment : Variable Environment을 복사한 후 변경 사항을 실시간으로 반영한 정보를 말한다. 일종의 백과사전, 공식문서 느낌으로 환경 정보들을 모아둔 것이다. 현재 컨텍스트 내부에 어떤 식별자들이 있는지, 외부 정보는 어떤 것들을 참조하도록 구성돼있는지. 이런 것들을 나타낸다.

  • This Binding : 식별자가 바라봐야할 대상 객체를 의미하며 this에 대해서는 3장에서 자세하게 다룬다.

그리고 Variable Environment(이하 V.E) 와 Lexical Environment(이하 L.E)의 내부는 다음 두 가지로 이루어져 있다.

  • environment Record (이하 eR)
  • outer environment Reference (이하 oeR)

둘의 내부 구성이 같으므로 L.E의 내부 구성에 대해서만 조금 더 자세하게 알아본다.

environment Record와 호이스팅

현재 컨텍스트와 관련된 식별자 정보들이 저장된다.

식별자라는 것은 위에서도 언급했듯 매개변수의 이름과 선언된 함수, 그리고 변수명을 의미한다. eR은 식별자에 어떤 값이 할당될 것인지에 대해서는 관심 가지지 않으며, 식별자에만 관심을 가진다. 컨텍스트 내부를 순서대로 훑으며 수집하기 때문에 자바스크립트는 코드 실행 전부터 해당 환경에 속한 식별자들을 모두 알고 있게 된다.

이러한 현상(식별자 수집 과정)을 사람들이 이해하기 쉽도록 설명한 것이 ’호이스팅‘이라는 개념이다. 호이스팅이란 식별자들을 최상단으로 끌어올린다는 것을 의미한다. 인간은 위에서부터 아래로 읽는 것에 익숙하기 때문에 식별자 수집 과정도 위에서부터 아래로 읽어 좀 더 이해하기 쉽도록 가상으로 설명하는 것이다. 다른 표현으로는 ‘실행 컨텍스트가 활성화될 때 선언된 변수를 위로 끌어올린다’라고 말할 수도 있다.

앞서 eR은 식별자에만 관심을 가지지, 식별자에 어떤 값이 할당될 것인지는 관심 가지지 않는다고 했다. 따라서 호이스팅 발생시 변수명만 끌어올려지며 할당과정은 원래 위치를 그대로 유지한다.

var a = 3;

//위의 코드의 호이스팅 과정은 아래와 같다.

var a;
a = 3;

여기서 var, let, const의 차이도 알 수 있는데, var 변수는 LE가 활성화될 때 eR에 의해 변수 선언부가 호이스팅되고 동시에 undefined로 초기화된다. 그렇기 때문에 선언 전에 사용해도 참조 에러가 발생하지 않는다.

하지만 let, const는 undefined를 할당하지 않은 채로 변수 선언부만 호이스팅된다. let, const는 호이스팅 되지 않는다는 설명들을 종종 볼 수 있지만 엄밀하게 말해 let, const도 호이스팅이 되긴 한다고 한다. 다만 초기화가 이루어지지는 않기 때문ReferenceError: Cannot access 'let변수' before initialization 이런 에러가 뜬다. 따라서 이후 변수 할당부에서 값을 할당하기 전까지는 사용할 수 없으며 만약 이를 무시하고 접근할 경우 참조 에러가 발생한다.

하지만 식별자 중에서 함수는 그렇지 않다. 함수 선언은 함수 전체를 끌어올린다. 그리고 호이스팅이 끝난 상태에서의 함수 선언문은 함수명으로 선언한 변수에 함수를 할당한 것처럼 여길 수 있다.

function test() {
  // 어쩌고
}

// 위의 코드의 호이스팅 과정은 아래와 같다.

var test = function test() {
  // 어쩌고
};

이로부터 함수 선언식과 함수 표현식의 차이를 알 수 있다. 함수는 하나의 값으로 취급 가능하다는 말도 이해할 수 있다.

함수표현식은 변수에 함수를 할당하는 것이므로 변수만 호이스팅되지만, 함수 선언식은 전체가 호이스팅되기 때문이다.

따라서 함수 표현식은 변수부가 끌어올려질 때 undefined가 할당되고, 선언부에서 선언되므로 선언 전 사용시 is not a function이라는 에러가 발생하지만 함수 선언식은 에러가 발생하지 않는다.

이러한 차이로 인해서 협업시 함수 표현식을 사용하는 것이 더 안전하다. 또한 전역 공간에 함수 선언식을 작성하거나 동명의 함수를 중복 선언하는 것은 지양된다.

outer environment Reference

oeR은 호출된 함수가 선언될 시점의 L.E를 참조하는 것을 가능하게 하는 개념으로, 스코프와 스코프체인과 연관된다.

  • 스코프 : 식별자에 대한 유효범위. ES5까지는 함수에 의해 생성되는 함수 스코프만 있었지만 ES6부터는 const, let에 의해 생성되는 block 스코프도 있다.

  • 스코프 체인 : 식별자의 유효범위를 안에서 밖으로 차례대로 검색하는 것을 말한다. 만약 가장 안쪽의 함수에서 변수 a에 대해 접근하고자 하는데 a를 찾을 수 없다면 oeR에 의해 가장 가까운 외부 L.E를 탐색한다. 그곳에서도 찾을 수 없다면 또 가장 가까운 외부로 나가며, 그렇게 전역 L.E에 닿을 때까지 찾지 못한다면 undefined를 반환한다.

  • 함수가 선언될 당시의 시점이라고 과거형을 사용한 이유는 위에서 이야기했듯이 선언된다는 것의 의미가 콜스택 상에서 어떤 실행 컨텍스트가 활성화 된 상태를 의미하기 때문이다.

  • 선언 시점의 L.E를 계속해서 찾아 올라가는 것을 스코프체인이라고 하는데 그러다보면 그 끝은 전역 컨텍스트의 L.E이다.

  • oeR은 가장 가까운 요소부터 차례대로만 접근할 수 있다. 만약 동일한 식별자가 선언됐을 경우에도 무조건 스코프체인상 가장 먼저 발견된 식별자에게만 접근가능하다. 이런 점을 활용하면 변수 은닉화를 구현할 수 있다.

var a = 1;

var outer = function () {
  var inner = function () {
    var a = 3;
  };
  inner();
};

outer();

이런 코드가 있다고 했을 때 inner 스코프 안에서 접근할 수 있는 변수 a는 3 뿐이다. 변수 a가 전역에도 1로 존재하지만 그것에는 접근할 수 없다. 이것을 변수 은닉화라고 한다.

그리고 이를 보면 전역 변수와 지역 변수에 대해서도 설명할 수 있다. 전역 컨텍스트의 L.E에 담긴 var a = 1; 과 같은 것을 전역 변수라고 하고, 함수에 의해 생성된 실행 컨텍스트의 L.E에 담긴 var b = 3;과 같은 것을 지역 변수라고 한다.

정리

이로써 코어자바스크립트에서 설명하는 실행 컨텍스트에 대해 정리해보았다. 글의 초입부에서 실행 컨텍스트에 대해 공부함으로써 해소되는 의문점이라고 했던 것에 대해 간략하게 답변을 달아보고 글을 마치겠다.

  • 호이스팅이란?
    자바스크립트가 식별자를 수집하는 과정을 사람이 이해하기 쉽게 설명하고자 만든, 변수 선언부를 자바스크립트 최상단으로 끌어올린다는 가상의 개념.
  • 전역변수와 지역변수란?
    전역 컨텍스트의 Lexical Environment에 담긴 값을 전역변수라 하고 그 밖의 함수에 의해 생성된 실행 컨텍스트의 변수들은 모두 지역변수이다.
  • 스코프란?
    변수의 유효범위를 말한다. 만약 스코프 내에서 변수를 찾지 못하면 outerEnvironmentReference에 의해 가장 가까운 상위 스코프의 Lexical Environment에서 값을 탐색한다.
  • let, const var의 차이?
    기본적으로 모두 호이스팅되나, var은 undefined로 초기화되는 반면 let과 const는 초기화가 일어나지 않는다. 따라서 var는 선언 전 호출해도 참조에러가 발생하지 않는 반면 let과 const는 초기화 해달라는 참조 에러가 발생한다. 또한 var는 중복해서 선언할 수 있지만 let은 불가능하다.
  • 함수선언식과 함수표현식의 차이?
    함수선언식은 함수가 통째로 호이스팅되지만 함수표현식은 함수를 할당하는 변수부만 호이스팅되며 함수를 할당하는 부분은 원래 자리에서 이뤄진다.
Profile picture

emewjin

Frontend Developer

잘못된 내용 혹은 더 좋은 방법이 있으면 언제든지 알려주세요 XD