1. javascript 질문과 답안 (6)

2023. 1. 26. 18:43기술면접 준비

728x90
반응형

1. 실행 컨텍스트에 대해 설명해주세요.

📌 실행 컨텍스트(execution context)는 실행할 코드에 제공할 환경 정보들을
모아놓은 객체로, 자바스크립트의 동적 언어로서의 성격을 가장 잘 파악할 수 있는
개념이다.
- 정재남, 코어자바스크립트

실행 컨텍스트는 자바스크립트 코드가 실행되는 환경이다. 모든 JavaScript 코드는 실행 컨텍스트 내부에서 실행된다고 생각하면 된다.

즉 함수가 실행되면 함수 실행에 해당하는 실행 컨텍스트가 생성되고, 자바스크립트 엔진에 있는 콜 스택에 차곡차곡 쌓인다.

그리고 가장 위에 쌓여있는 컨텍스트와 관련 있는 코드를 실행하면서(LIFO), 전체 코드의 환경과 순서를 보장하게 된다.
실행 컨텍스트는 식별자(변수, 함수, 클래스 등의 이름)를 등록하고 관리하는 스코프와 코드 실행 순서 관리를 구현한 내부 매커니즘으로, 실행 컨텍스트는 곧 자바스크립트의 핵심 원리다.

2. 실행 컨텍스트는 언제 생성될까?

자바스크립트 엔진이 스크립트를 처음 마주할 때 전역 컨텍스트를 생성하고, 콜 스택에 push 한다.
엔진이 스크립트를 쭉 읽어내려가면서 함수 호출을 발견할 때마다, 함수의 실행 컨텍스트를 스택에 push 한다.
중요한 점은 함수 실행 컨텍스트는 함수가 실행될 때! 만들어진다는 점이다.

함수를 선언할 때가 아니라 실행할 때다.

실행 컨텍스트를 만들 수 있는 방법으로는 1) 전역공간, 2) 함수, 3) eval() 함수가 있다.
이 중에서 eval 함수는 문자열로 된 자바스크립트 코드를 전달하면 그게 그대로 실행되는 함수인데, 속도나 보안이 좋지 않아 현재는 거의 쓰지 않는다. 없다고 생각하자

"eval is evil" - Douglas Crockford

따라서 자동으로 생성되는 전역공간과 eval을 제외하면, 실행 컨텍스트가 생성되는 시점은 곧 함수를 실행하는 시점이다.

3. 실행 컨텍스트의 종류

앞서 실행 컨텍스트를 만들 수 있는 방법으로 전역공간, 함수, eval()이 있다고 했다.
따라서 실행 컨텍스트도 3가지로 나뉜다.

  • 전역 실행 컨텍스트: 전역 영역에 존재하는 코드.
    모든 스크립트 코드는 전역 실행 컨텍스트 안에서 실행된다.
    프로그램에 단 한 개만 존재하며 실행 컨텍스트의 기본이다. 함수 밖에 있는 코드는 전역 실행 컨텍스트에 있다.
    브라우저의 경우에는 window객체, Node.js의 경우엔 global객체가 곧 전역 실행 컨텍스트가 된다.
  • 함수 실행 컨텍스트: 함수 내에 존재하는 코드.
    함수가 실행될 때마다 만들어지는 실행 컨텍스트이다.
    각 함수는 고유의 실행 컨텍스트를 가지며, 함수가 실행되거나 call 될 때에만 생성된다.
  • eval() 실행 컨텍스트: eval 함수로 실행되는 코드.
    이제 쓰지 않는 eval() 함수에 의해 생성되는 실행 컨텍스트이다.

4. 실행 컨텍스트의 구조

실행 컨텍스트는 어떻게 이루어져 있을까?
실행 컨텍스트는 LexicalEnvironment 컴포넌트와 variableEnvironment 컴포넌트로 구성된다.
아래의 그림을 참고하자.

함수가 호출되면 전역 공간에 있던 코드의 제어권이 함수의 내부로 이동하면서 함수 코드를 평가하기 시작한다. 함수 코드 평가는 아래 순서대로 진행된다.

1) 함수 실행 컨텍스트 생성
2) 함수 LexicalEnvironment 생성
2-1) 함수 environmentRecord 생성
2-2) ThisBinding
2-3) outerEnvironmentReference 결정

더 자세하게 각각의 요소들을 알아보자.

  • variableEnvironment: 현재 컨텍스트 내의 식별자들에 대한 정보 + 외부 환경 정보를 담는다.
    선언 시점의 LexicalEnvironment의 스냅샵으로 변경 사항은 반영되지 않는다.
    실행 컨텍스트를 생성할 때 variableEnvironment에 정보를 먼저 담은 다음,
    이를 복사해서 LexicalEnvironment를 만든다.
    • environment record (snapshot)
    • outer environment reference (snapshot)
  • LexicalEnvironment(어휘적 환경): 처음에는 variableEnvironment와 같지만 변경 사항이
    실시간으로 반영된다.
    • environmentRecord(환경 레코드): 함수 안의 코드가 실행되기 전에 현재 컨텍스트와 관련된 코드의 식별자 정보가 저장된다. (매개변수의 이름, 함수 선언, 변수명 등)
      즉 코드가 실행되기 전에 자바스크립트 엔진은 이미 해당 환경에 속한 코드의 변수명 등을 모두 알고 있게 된다. (호이스팅)
    • outerEnvironmentReference(외부 렉시컬 환경에 대한 참조): 상위 스코프를 가리킨다. 즉 현재 environment record보다 바깥에 있는 environment record를 참고한다는 뜻이며 해당 실행 컨텍스트를 생성한 함수의 바깥 환경을 가리킨다.
      따라서 자바스크립트 엔진이 현재 렉시컬 환경에서 변수를 찾을 수 없다면 외부 환경에서 찾는다는 것을 뜻한다.
      만약 상위 스코프에서도 해당 식별자를 찾을 수 없다면 참조 에러(uncaught reference error)를 발생시킨다.
  • ThisBinding: this 식별자가 바라봐야 할 대상 객체. 실행 컨텍스트가 활성될 때 this가 지정되지 않은 경우 this에는 전역 객체가 저장된다.
  • 🏆 LexicalEnvironment 렉시컬 환경 (어휘적 환경)

    렉시컬 환경은 식별자와 식별자에 바인딩된 값, 그리고 상위 스코프에 대한 참조를 기록하는 자료구조로 실행 컨텍스트를 구성하는 컴포넌트이다. 실행 컨텍스트 스택이 코드의 실행 순서를 관리한다면 렉시컬 환경은 스코프와 식별자를 관리한다. 렉시컬 환경은 키와 값을 갖는 객체 형태의 스코프를 생성해서, 식별자를 키로 등록하고 식별자에 바인딩된 값을 관리한다.

5. 실행 컨텍스트의 생성과 작동 과정

이번에는 아래의 코드를 보며 실행 컨텍스트의 생성과 식별자 검색 과정을 알아보자.

const str = '안녕';

function outer() {
  function inner() {
    const greeting = '하이';
    console.log(greeting);
    console.log(str);
  }
  inner();
}
outer();

console.log(str);
  1. 시작: 전역 컨텍스트가 생성된다. 전역 컨텍스트의 environmentRecord에 { str, outer } 식별자를 저장한다. 전역 컨텍스트는 가장 최상위 컨텍스트이므로 outerEnvironmentReference는 null이다. (this: 전역객체)
  2. 1, 3번째 줄: 전역 스코프에 있는 변수 str에 '안녕'을, outer에 함수를 할당한다.
  3. 10번째 줄: outer 함수를 호출한다. 전역 컨텍스트의 코드는 10번째 줄에서 잠시 중단되고, outer 실행 컨텍스트가 활성화되어 3번째 줄로 이동한다.
  4. 3번째 줄: outer 실행 컨텍스트의 environmentRecord에 { inner } 식별자를 저장한다.
    outerEnvironmentReference에는 outer 함수가 선언될 당시의 LexicalEnvironment가 담긴다(===전역 컨텍스트의 LexicalEnvironment).
    이를 { GLOBAL, { a, outer }}라고 표기하자. 첫 번째는 실행 컨텍스트의 이름, 두 번째는 environmentRecord 객체다. (this: 전역객체)
  5. 4번째 줄: outer 스코프에 있는 변수 inner에 함수를 할당한다.
  6. 8번째 줄: inner 함수를 호출한다. 여기서 outer 실행 컨텍스트의 코드는 임시 중단되고, inner 실행 컨텍스트가 활성화되어 4번째 줄로 이동한다.
  7. 5번째 줄: inner 실행 컨텍스트의 environmentRecord에 { greeting } 식별자를 저장한다.
    outerEnvironmentReference엔 inner 함수가 선언될 당시의 LexicalEnvironment가 담긴다. inner함수는 outer함수에서 선언되었으므로
    outer 함수의 LexicalEnvironment, 즉 { outer, { inner }}를 참조복사한다. (this: 전역객체)
  8. 6번째 줄: environmentRecord에 있는 greeting을 찾아서 실행한다.
  9. 7번째 줄: 식별자 str에 접근하려고 한다. 이때 자바스크립트 엔진은 활성화된 실행 컨텍스트의 LexicalEnvironment에 접근한다.
    첫 요소의 environmentRecord에서 str이 있는지 찾아보고, 없으면 outerEnvironmentReference에 있는 environmentRecord로 넘어가는 식으로 계속해서 검색한다.
    여기서는 전역 LexicalEnvironment에 str이 있으므로 '안녕'을 출력한다.
  10. 8번째 줄: inner 함수 실행이 종료된다. inner 실행 컨텍스트가 콜 스택에서 제거되고, outer 실행 컨텍스트가 다시 활성화되면서 9번째 줄로 이동한다.
  11. 10번째 줄: outer 함수 실행이 종료된다. outer 실행 컨텍스트가 콜 스택에서 제거되고, 전역 컨텍스트가 다시 활성화 된다.
  12. 13번째 줄: 전역 컨텍스트의 environmentRecord에서 str을 검색해서 실행한다.
  13. 완료: 모든 코드의 실행이 종료되어 전역 컨텍스트가 콜 스택에서 제거되고 종료된다.

 

참고 자료

 

2. 호이스팅(hoisting)에 대해 설명해주세요.

 

호이스팅이란?

  • 호이스팅은 코드를 실행하기 전 변수선언/함수선언을 해당 스코프의 최상단으로 끌어올리는 것이 아니다.
  • 호이스팅은 코드가 실행하기 전 변수선언/함수선언이 해당 스코프의 최상단으로 끌어 올려진 것 같은 현상을 말한다.
  • 자바스크립트 엔진 코드를 실행하기 전 실행 가능한 코드를 형상화하고 구분하는 과정(*실행 컨텍스트를 위한 과정)을 거친다.
  • 자바스크립트 엔진은 코드를 실행하기 전 실행 컨텍스트를 위한과정에서 모든 선언(var, let, const, function, class)을 스코프에 등록한다.
  • 코드 실행 전 이미 변수선언/함수선언이 저장되어 있기 때문에 선언문보다 참조/호출이 먼저 나와도 오류 없이 동작한다.
    (정확히는 var 키워드로 선언한 변수와 함수 선언문일 경우 오류 없이 동작한다. 이는 선언이 파일의 맨 위로 끌어올려진 것 처럼 보이게 한다.)
  • *실행 컨텍스트는 실행 가능한 코드가 실행되기 위해 필요한 환경을 의미하고 실행되기전 이러한 실행 컨텍스트 과정(코드를 구분하는 과정)을 거친다.

이 호이스팅이라는 용어를 자바스크립트 실행 컨텍스트에 의한 위에 설명한 현상을 호이스팅이라고 부른다는 것으로 이해하면 되겠다. 그 현상이란 선언이 코드 실행 보다 먼저 메모리에 저장되는 과정으로 인한 현상을 말한다.

그래서 자바스크립트 실행 컨텍스트에 대한 이해하는 것이 호이스팅을 이해하는데 도움이 된다. (scope, hoisting, this, function, closure 등의 동작원리를 담고 있는 자바스크립트 핵심원리이다. 코드를 보고 실행 컨텍스트를 이해할 수 있어야 코드 독해, 디버깅을 할수 있다.)

❗️변수 호이스팅 (var, let, const 키워드)

  • 자바스크립트의 모든 선언에는 호이스팅이 일어난다.
  • 그런데 let, const, class를 이용한 선언문 호이스팅이 발생하지 않는 것처럼 동작한다.
  • var 키워드로 선언된 변수와는 달리 let 키워드로 선언된 변수를 선언문 이전에 참조하면 참조 에러(ReferenceError)가 발생한다.
  • 이는 let 키워드로 선언된 변수는 스코프의 시작에서 변수의 선언까지 *일시적 사각지대(Temporal Dead Zone; TDZ)에 빠지기 때문이다.

여기서 중요한 지점은 이 호이스팅이라는 용어가 ‘선언이 먼저 메모리에 저장되었다.’는 것을 의미하기 때문에 즉, ‘선언이 끌어올려진다’는 의미이기 때문에 모든 선언은 호이스팅이 일어난다는 말은 참이된다.

즉, 호이스팅이 파일의 맨 위로 끌어올려진 것 같은 현상을 의미할 때 선언문 이전에 참조해서 에러를 발생시킨다고 호이스팅이 일어나지 않은 것은 아니라는 의미이다.

  • 왜냐하면 정말 선언은 끌어올려진 것이 맞다. (표현하면 그렇고 정확히는 선언이 코드 실행 전에 메모리에 저장되었다는 의미이다.)
  • 그런데 왜 오류가 나는가 하면 var 키워드는 선언과 함께 undefined로 초기화되어 메모리에 저장되는데 let과 const는 초기화되지 않은 상태로 선언만 메모리에 저장되기 때문이다.
  • 초기화 되지 않으면 변수를 참조할 수 없다. 그래서 참조 에러를 일으키는 것이다.
  • let과 const에도 호이스팅이 일어나기 때문에 에러를 일으키는 것이다.
  • 호이스팅이 일어나지 않는다면 아래 코드에서 에러가 발생하지 않을 것이다.
  • 선언이 호이스팅되었기 대문에 블록 스코프에서 foo를 찾을 수 없는 것이다.
let foo = 1;
{
console.log(foo);
let foo = 2;
}
 

위 예시 덕분에 호이스팅의 개념이 정리가 되었다.
호이스팅이 ‘현상’을 의미하는 것이라면 변수 키워드 중 var 만이 에러 없이 변수 선언이 상단으로 끌어올려지는 것 같은 ‘현상’이 일어나기 때문에 var 만이 호이스팅이 일어난다고 생각했다.
하지만 위 예시에서 알 수 있었고 자바스크립트 엔진이 동작하는 방식을 알고보니 let 키워드에서도 상단으로 끌어올려진 것 같은 ‘현상(즉, 호이스팅)’이 발생했기 때문에 에러가 발생하는 것이라는 것을 알게되었다.
(에러가 난다고 호이스팅이 되지 않은 것이 아닌, 호이스팅이 발생했기 때문에 에러가 발생한 것이다.)

❗️변수는 어떻게 생성되며, 호이스팅은 어떻게 이뤄질까?

  • 변수는 3단계에 걸쳐 생성된다.

1단계: 선언 단계(Declaration phase)

  • 변수를 실행 컨텍스트의 변수 객체에 등록한다.
  • 이 변수 객체는 스코프가 참조하는 대상이 된다.

2단계: 초기화 단계(Initialization phase)

  • 변수 객체에 등록된 변수를 위한 공간을 메모리에 확보한다.
  • 이 단계에서 변수는 undefined로 초기화 된다.

3단계: 할당 단계(Assignment phase)

  • undefined로 초기화된 변수에 실제 값을 할당한다.

 

var 키워드로 선언한 변수는 선언 단계와 초기화 단계가 한번에 이뤄진다. 즉, 스코프에 변수를 등록(선언 단계)하고 메모리에 변수를 위한 공간을 확보한 후, undefined로 초기화한다. 따라서 변수 선언문 이전에 변수에 접근하여도 스코프에 변수가 존재하기 때문에 에러가 발생하지 않는다. 다만 undefined를 반환한다. 이후 변수 할당문에 도달하면 비로소 값이 할당된다.

 

let 키워드로 선언된 변수는 선언 단계와 초기화 단계가 분리되어 진행된다. 즉, 스코프에 변수를 등록(선언 단계)하지만 초기화 단계는 변수 선언문에 도달했을 때(코드 실행 후) 이뤄진다. 초기화 이전에 변수에 접근하려고 하면 참조 에러가 발생한다. 이는 아직 변수가 초기화되지 않았기 때문이다. 즉, 변수를 위한 메모리 공간이 아직 확보되지 않았기 때문이다. 따라서 스코프의 시작 지점부터 초기화 시작 지점까지는 변수를 참조할 수 없다. 스코프의 시작 지점부터 초기화 시작 지점까지의 구간을 ‘일시적 사각지대(Temporal Dead Zone; TDZ)’라고 부른다.

 

 

https://velog.io/@edie_ko/js-execution-context

 

실행 컨텍스트란 무엇인가요?

📌 자바스크립트의 핵심 원리, 실행 컨텍스트에 대해 알아봅니다.

velog.io

https://hanamon.kr/javascript-%ED%98%B8%EC%9D%B4%EC%8A%A4%ED%8C%85%EC%9D%B4%EB%9E%80-hoisting/

 

[JavaScript] 호이스팅(Hoisting)이란? - 하나몬

❗️호이스팅이란? 호이스팅은 코드를 실행하기 전 변수선언/함수선언을 해당 스코프의 최상단으로 끌어올리는 것이 아니다. 호이스팅은 코드가 실행하기 전 변수선언/함수선언이 해당 스코프

hanamon.kr

 

728x90
반응형