Study | 실행 컨텍스트 #1

grighth12·2021년 8월 8일
4

article

목록 보기
1/4
post-thumbnail

우리가 자바스크립트를 이해하기 위해, 절대 피할 수 없고 반드시 알아야 하는 실행 컨텍스트에 대해 알아볼까요?

실행 컨텍스트 왜 알아야 해요?

이번 주에 제가 겼었던 실제 사례로 한 번 알아보겠습니다!

저희 팀은 오전 11시에 따로 모여서 이야기 하는 시간을 가지는데요. 저와 다슬님이 그 전날 올바른 괄호 문제를 풀면서 고민했던 주제를 공유해 보았습니다.

잠깐 멈추시고 아래 코드가 어떻게 작동될 지 생각해보세요. 아래 코드는 올바른 괄호 문제의 정답이 될 수 있을까요?

코드를 이렇게 짜고 제출하자 당연하게도(!) 오답이 나왔습니다. 아마 보자마자 '이건 당연히 안되지!' 라고 생각하시는 분도 계시겠지만, 저희 중 대부분은 한참을 헤매면서 아래와 같은 과정을 거쳐 문제를 해결했습니다.

  1. 반례를 찾아본다.
  2. 로그를 찍어본다.
  3. 어…! forEach 콜백 함수가 return false를 하는 때에 solution 함수가 종료될 거라 생각했는데 안되잖아?
  4. 아하, forEach의 콜백 함수가 return 된다고 해서 solution 함수가 종료되는 건 아니었군.
  5. forEach 문이 아니라 for... of 문으로 바꿔줘야 겠다.

우리는 이러한 문제를 왜 사전에 예측하지 못 할까요? 그건 바로 우리가 코드 독해 능력이 부족하기 때문입니다. 우리가 코드를 작성할 때 예상과 코드의 동작이 같도록 하는 것은 중요한 역량일 것입니다. 이러한 코드 독해 능력은 자바스크립트의 동작 원리 핵심 개념인 실행 컨텍스트를 알게 된다면 향상시킬 수 있습니다. 또 오류가 생겼을 때 디버깅도 쉽게 할 수 있게 되겠죠?

실행 컨텍스트의 개념부터 차근차근 알아가 봅시다🚀


실행 컨텍스트의 개념

실행 컨텍스트는 간단하게 말하면 소스코드가 실행되기 위해 필요한 환경입니다. 어디서 식별자를 찾아야 하는지, 소스코드가 실행되는 순서가 어떻게 되는지를 관리하는 것입니다. 아직 무슨 말인지 잘 와닿지 않을 수 있어요. 소스코드가 실행되는 과정을 먼저 알아봅시다.

소스코드의 실행과정

소스코드의 실행 과정은 크게 두 가지로 나뉩니다. 소스코드의 평가와 소스코드의 실행입니다. 각각의 단계에서는 다음과 같은 일들이 일어납니다.

  1. 소스코드의 평가
    가장 먼저 실행 컨텍스트를 생성하고, 선언문을 실행 컨텍스트에 등록합니다.

  2. 소스코드의 실행
    평가가 끝나면 선언문 이외의 문에 대한 소스코드를 실행합니다. 아까 실행 컨텍스트에 등록해놓았던 변수나 함수를 참조하여 소스코드를 실행하고, 실행 결과 등록 변수 값 변경과 같은 실행 결과를 실행 컨텍스트에 반영합니다.


    출처 -자바스크립트 딥 다이브 23장, 361p


실행 컨텍스트의 구성

실행 컨텍스트는 실행 컨텍스트 스택과 렉시컬 환경으로 이루어져있습니다.
실행 컨텍스트 스택은 소스코드 실행 순서 변경을 관리하는 영역입니다. 렉시컬 환경식별자와 스코프를 관리하는 영역입니다.

이해가 안 간다면, 코드를 통해 각각 알아봅시다.


실행 컨텍스트 스택


실행 컨텍스트 스택(이하 콜 스택)이 어떻게 코드의 실행 순서를 관리하는지 예제를 통해 확인해봅시다.


출처 -자바스크립트 딥 다이브 23장, 364p

  1. 전역 실행 컨텍스트 콜 스택에 push(생성)

    1. 전역 코드 평가
      • 선언문을 찾은 후, x : <uninitialized>foo : <function object>를 전역 실행 컨텍스트에 등록합니다.
      • 이 때, foo 함수 내부의 코드는 아직 평가 되지 않습니다. 함수 코드는 되는 시점에 평가됩니다.
    2. 전역 코드 실행
      • x = 1 할당문을 실행합니다. x가 1로 변했으니 실행 컨텍스트에도 반영(x : 1)합니다. foo(); 호출을 실행합니다.
  2. foo 함수 실행 컨텍스트 콜 스택에 push(생성)

    1. foo 함수 코드 평가
      • 선언문 y : <uninitialized>bar : <function object>foo 함수 실행 컨텍스트에 등록합니다.
    2. foo 함수 코드 실행
      • y = 2 할당문을 실행합니다. y가 2로 변했으니 실행 컨텍스트에도 반영(y : 2)합니다. 다음으로 bar(); 호출을 실행합니다.
  3. bar 함수 실행 컨텍스트 콜 스택에 push(생성)

    1. bar 함수 코드 평가
      • 선언문인 z : <uninitialized>를 실행 컨텍스트에 등록합니다.
    2. bar 함수 코드 실행
      • z = 3 할당문을 실행합니다. z가 3으로 변했으니 실행 컨텍스트에서도 반영(z : 3)합니다. 다음으로 실행 컨텍스트에서 x, y, z를 찾아 x+y+z를 평가합니다. 이후 console.log를 실행합니다.
  4. bar 함수 실행 컨텍스트 콜 스택에서 pop

    • bar 함수가 종료됩니다.
  5. foo 함수 실행 컨텍스트 콜 스택에서 pop

    • foo 함수가 종료됩니다.
  6. 전역 실행 컨텍스트 pop

    • 전역 코드가 종료됩니다.

위와 과정에서 우리는 전역 -> foo 함수 -> bar 함수 -> foo 함수 -> 전역으로 코드의 실행 순서가 제어되는 것을 확인할 수 있습니다.

이처럼 자바스크립트는 콜 스택을 이용해 코드의 실행 순서를 제어합니다.


렉시컬 환경


렉시컬 환경은 변수와 스코프를 관리하는 영역입니다.
우리가 콜 스택을 설명할 때, 변수와 함수를 등록하고, 참조하여 함수를 실행하고, 변경 된 값을 반영하는 과정을 설명했었는데요. 이 과정이 바로 렉시컬 환경에서 일어납니다. 렉시컬 환경은 실행 컨텍스트가 생성될 때, 실행 컨텍스트와 바인딩 되어 생성됩니다.

코드와 그림으로 알아봅시다.

  1. 전역 코드
    1. 소스코드의 평가

      • xfoo를 전역 실행 컨텍스트와 바인딩 된 전역 렉시컬 환경에 등록합니다.

    2. 소스코드의 실행

      • x = 1을 실행하고, foo를 호출합니다.

  2. foo 함수 코드
    1. 소스코드의 평가

      • yfoo 함수 실행 컨텍스트와 바인딩 된 foo 렉시컬 환경에 등록합니다.
      • foo를 렉시컬 스코프(상위 스코프)와 연결합니다. 이를 스코프 체인이라고 합니다.

    2. 소스코드의 실행
      - y = 2를 실행합니다.
      - console.log를 실행하기 전, x+y를 평가하기 위해서, 각각의 변수를 찾기 위해 일단 자신의 렉시컬 환경(foo의 렉시컬 환경)을 찾아갑니다. x는 자신의 렉시컬 환경에서 찾았지만, y는 본인의 렉시컬 환경에 존재하지 않습니다. 이 경우에 아까 연결해놓았던 스코프 체인을 통해 상위 렉시컬 환경으로 이동하고, x를 찾습니다. x+y를 평가하여 console.log 를 실행합니다.


      출처 -자바스크립트 딥 다이브 23장, 366p

이처럼 변수와 함수는 각각 실행 컨텍스트의 렉시컬 환경에서 관리됩니다. 실행 컨텍스트와 바인딩 된 렉시컬 환경이 스코프의 실체입니다. 렉시컬 환경이 해제 될 때, 등록되어 있는 변수와 함수도 당연히 함께 해제됩니다.


실행 컨텍스트의 물리적 구조

렉시컬 환경을 조금 더 물리적인 형태로 보면 다음과 같이 구성되어있습니다.

실행 컨텍스트에는 LexicalEnvironment, VariableEnvironment 프로퍼티가 있고, 이는 렉시컬 환경과 연결되어 있습니다. 렉시컬 환경에는 크게 EnvironmentRecord, OuterLexicalEnvironmentReference가 있습니다.

EnvironmentRecord는 스코프에 포함된 식별자를 등록하고 등록된 식별자에 바인딩 된 값을 관리하는 영역입니다. x, foo와 같은 식별자가 등록되고 값이 바뀔 경우에 반영되는 영역이 이 곳입니다.

OuterLexicalEnvironmentReference는 렉시컬 스코프(상위 스코프)를 가리킵니다. 아까, 렉시컬 환경을 설명하며 살펴봤던 스코프 체인이 실제로는 OuterLexicalEnvironmentReference 영역에 참조됩니다.

실행 컨텍스트 시각화

Javascript Visualizer에서 코드를 입력하시고 플레이 해보시면, 실제 자바스크립트가 어떻게 실행 컨텍스트를 생성하고 구성되는지 간결하게 볼 수 있습니다. (아쉽게도 ES5 문법만 지원됩니다.)

제가 여기서 설명하지 않은 다양한 property들도 보시게 될 텐데요. 2편에서 관련된 내용(전역 객체, 전역 실행 컨텍스트와 함수 컨텍스트의 차이 등)으로 찾아 뵙도록 하겠습니다!

마치며

스터디 주제를 고르기에 앞서, 교육 매니저 리아님이 잘 아는 주제 말고 잘 모르는 주제를 선택해 보라는 조언을 주셨는데요. 그래서 처음 들어보는 실행 컨텍스트를 선정했었습니다. 조금 더 다양한 자료를 접해서 7분 이내에 전달할 수 있게 좀 더 추상화 했다면 좋았을텐데 하고 아쉬움이 남습니다. 이번 주에는 책 내용 이해만으로도 좀 벅찼던 것 같습니다. 하지만 차근차근 살펴보면 정말 쉬운 주제라고 생각합니다.

여기에 포함된 내용만 보더라도 재귀, 스코프와 클로저에 대한 막연함은 없앨 수 있습니다!

마지막으로 저희 첫 번째 문제에 대한 답을 실행 컨텍스트 관점에서 생각해보면 다음과 같습니다.

solution 함수 호출문이 코드의 가장 아래에 있다는 가정하에 설명합니다.

  1. 전역 실행 컨텍스트가 콜 스택에 쌓이고 solution 함수가 전역 렉시컬 환경에 등록됩니다.
  2. solution 함수가 실행, solution 함수 실행 컨텍스트가 콜 스택에 쌓입니다.
  3. stack, str 선언문을 solution 함수 렉시컬 환경에 등록합니다.
  4. stack 할당문을 실행하여 함수 렉시컬 환경에 변경을 반영합니다.
  5. s.split이 실행되어 split 함수가 콜 스택에 쌓이고, 종료되어 pop됩니다. 반환 결과가 str에 할당되고 solution 함수 렉시컬 환경에 변경을 반영합니다.
  6. s.forEach가 실행되어 forEach 함수가 콜 스택에 쌓입니다.
  7. forEach의 콜백 함수 실행 컨텍스트가 쌓이고, 종료되어 pop 됩니다. strlength만큼 반복됩니다.
    • return 문에 들어가더라도 콜백 함수가 종료될 뿐 solution 함수가 종료되지 않습니다. 스택에 쌓이는 과정을 생각해보시면 당연하게 느껴지실 거예요! return 값이 어딘가에 할당되지도 않으니 활용될 수 없습니다.
  8. forEach 함수가 종료 되어 콜스택에서 pop 됩니다.
  9. solution 함수는 stack.length === 0 을 평가하여 return 합니다.
  10. solution 함수가 종료 되어 콜 스택에서 pop됩니다.

이제 여러분도 다들 아시겠죠?! 그렇다면 정말 뿌듯할 것 같습니다..😊


참고 자료 및 강의

자바스크립트 딥다이브 23장 실행 컨텍스트
https://poiemaweb.com/js-execution-context



profile
데굴데굴 굴러가고 있습니다

0개의 댓글