[JS스터디] 실행 컨텍스트, 스코프

2023. 4. 4. 11:47JavaScript

 

실행 컨텍스트

: 소스코드를 실행하는 데 필요한 환경을 제공하고, 코드의 실행 결과를 실제로 관리하는 영역 (메커니즘)

모든 코드는 실행 컨텍스트에 의해 실행관리된다.

(여기서 관리란, 식별자에 바인딩된 값의 변화 즉 변수나 함수에 할당된 값이 변경되는 것을 추적하는 것, 상태 변경 추적)

소스코드의 타입

: 전역코드, 함수코드, eval코드, 모듈코드

소스코드를 4가지로 분류하는 이유: 타입에 따라 실행 컨텍스트를 생성하는 과정과 관리하는 내용이 다르다

 

각 소스코드는 평가되어 각각의 실행 컨텍스트를 생성한다.

전역코드는 평가되어 전역 실행 컨텍스트를, 

함수코드는 평가되어 함수 실행 컨텍스트를, 

eval코드는 평가되어 eval 실행 컨텍스트를, 

모듈코드는 평가되어 모듈 실행 컨텍스트를 생성한다.

소스코드의 평가와 실행

모든 소스코드는 평가과정과 실행과정을 거친다.

자바스크립트 엔진은 코드를 실행하기 이전에 평가과정을 거친다.

 

<평가과정에서는> 실행 컨텍스트를 생성 → 변수, 함수 선언문 먼저 실행 → 생성된 식별자를 키로 실행 컨텍스트가 관리하는 스코프에 등록

<실행과정에서는> 실행에 필요한 정보, 즉 변수, 함수의 참조를 스코프에서 검색하고 취득 / 변수 값의 변경 등 실행 결과는 다시 스코프에 등록

실행 컨텍스트의 역할

소스코드를 실행시키고 관리한다.

소스코드를 실행시키기 위해 1. 스코프, 2. 식별자, 3. 코드 실행 순서를 관리한다.

1. 스코프 ▶ 선언에 의해 생성된 모든 식별자의 스코프를 구분하여 등록 및 상태 변경 추적 

2. 식별자 ▶ 중첩 관계에 의해 스코프 체인 형성, 스코프 체인을 통해 상위 스코프에서 식별자 검색할 수 있어야

3. 코드 실행 순서 ▶ 순서를 변경하고 다시 되돌아올 수 있어야

 

코드 실행 순서는 실행 컨텍스트 스택이,

스코프와 식별자는 렉시컬 환경이 관리한다.

이는 아래에서 자세히 살펴본다.

실행 컨텍스트 스택 stack

함수가 포함된 소스코드의 경우,

전역코드가 평가되고 전역 실행 컨텍스트가 생성되고,

함수가 호출되면 함수코드가 평가되고 함수 실행 컨텍스트가 생성된다.

이러한 코드의 실행 순서는 스택 자료구조로 관리된다.

이를 실행 컨텍스트 스택이라 한다.

 

실행 컨텍스트 스택에는 소스코드가 평가되어 생성된 실행 컨텍스트가 쌓이고(push) 실행이 끝나면 제거(pop)된다.

실행 컨텍스트 스택의 최상위 노드는 항상 현재 실행 중인 코드의 실행 컨텍스트이다.

렉시컬 환경

: 코드가 어디서 실행되며 주변에 어떤 코드가 있는지 (코드의 문맥)

: 식별자와 식별자에 바인딩된 값, 상위 스코프에 대한 참조를 기록하는 자료구조

: 실행 컨텍스트를 구성하는 컴포넌트

 

렉시컬 환경은 키와 값을 갖는 객체 형태의 스코프를 생성하여 식별자를 키로 등록하고 식별자에 바인딘된 값을 관리한다.

렉시컬 환경은 스코프를 구분하여 식별자를 등록하고 관리하는 저장소 역할을 하는 렉시컬 스코프의 실체이다.

 

렉시컬 환경의 구성 컴포넌트

1. 환경 컴포넌트

  : 스코프에 포함된 식별자 등록, 등록된 식별자에 바인딩된 값을 관리하는 저장소

2. 외부 렉시컬 환경에 대한 참조

  : 상위 스코프를 가리킴 (상위 코드의 렉시컬 환경)

실행 컨텍스트의 생성과 식별자 검색 과정

1. 전역 객체 생성

  : 빌트인 전역 프로퍼티, 빌트인 전역 함수 등 포함

2. 전역 코드 평가

  : 전역 실행 컨텍스트 생성 → 전역 렉시컬 환경 생성 (전역 환경 레코드 생성 → this 바인딩 → 외부 렉시컬 환경에 대한 참조 결정)

3. 전역 코드 실행

  3-1. foo 함수 코드 평가

  3-2. foo 함수 코드 실행

  3-3. bar 함수 코드 평가

  3-4. bar 함수 코드 실행

  3-5. bar 함수 코드 종료

  3-6. foo 함수 코드 종료

4. 전역 코드 종료

실행 컨텍스트의 블록 레벨 스코프

var 키워드 선언 변수는 오직 함수의 코드 블록을 지역 스코프로 인정하는 함수 레벨 스코프를 따른다.

let, cosnt 키워드 선언 변수는 모든 코드 블록을 지역 스코프로 인정하는 블록 레벨 스코프를 따른다.

 


스코프

: 식별자가 유효한 범위

: 식별자를 검색하는 규칙

 

모든 식별자는 자신이 선언된 위치에 의해 유효범위(다른 코들가 식별자 자신을 참조할 수 있는 범위)가 결정된다.

자바스크립트 엔진이 식별자를 검색할 때 사용하는 규칙으로, 스코프를 통해 어떤 변수를 참조해야 하는지를 결정한다.

 

스코프가 필요한 이유?

스코프가 없으면 우리는 프로그래밍 전체에서 겹치는 식별자 이름이 단 한 개도 없게 해야하는데, 이것은 매우 어려운 일이다.

식별자마다 스코프를 지정해두어 스코프가 다르면 같은 이름의 식별자를 사용할 수 있도록 한 것이다.

즉, 스코프는 네임스페이스다.

 

* var 키워드는 같은 스코프 내에서 중복 선언이 허용된다. 반면 let이나 const로 선언된 변수는 같은 스코프 내에 중복 선언을 허용하지 않는다.

 

스코프의 종류

: 전역변수, 지역변수

 

자신이 선언된 위치에 의해 유효 범위인 스코프가 결정된다.

전역 ( = 코드의 가장 바깥 영역 )에서 선언되면 전역 스코프를 갖는 전역 변수이고,

지역 ( = 함수 몸체 내부 )에서 선언되면 지역 스코프를 갖는 지역 변수이다.

* 여기서 함수 몸체 내부라고 함은, 지역 스코프가 단순 코드 블록이 아닌 오직 함수에 의해서만 생성된다는 의미이다.

 

* 지역 변수는 자신의 지역 스코프와 하위 지역 스코프에서 유효하다.

* 중첩 함수 내부에 선언된 x 변수 외에 이름이 같은 전역 변수 x가 존재할 경우, 중첩 함수 내부에서 x를 참조하면 전역 변수가 아닌 지역 변수 x를 참조하게 된다. 자바스크립트 엔진이 스코프 체인을 통해 참조할 변수를 탐색했기 때문.

 

스코프 체인

: 스코프가 계층적으로 연결된 것

 

함수가 중첩될 경우 스코프가 그에 의해 계층적인 구조를 갖게 된다.

외부 함수의 지역 스코프를 중첩 함수의 상위 스코프라 한다.

 

변수를 참조할 때 자바스크립트 엔진은 스코프 체인을 타고

변수를 참조하는 코드의 스코프부터 시작해서 상위 스코프 방향으로 이동하여 선언된 변수를 검색한다. (절대 하위 방향이 될 수 없다 = 상위 스코프에서 하위 스코프의 변수를 참조할 수 없다)

이를 통해 상위 스코프에서 선언된 변수를 하위 스코프에서도 참조할 수 있다.

 

스코프 체인에 의한 변수 검색 과정

참조하는 코드의 스코프부터 시작해서 선언된 변수가 있는지 검색 → 찾으면 검색된 변수를 참조하고 → 검색 종료

 

스코프 체인에 의한 함수 검색

함수도 식별자에 해당하기 때문에 동일하다.

즉 내부 함수와 동일한 식별자의 함수가 전역 스코프에 있는 경우, 외부 함수에서 내부 함수를 호출하면 스코프 체인을 통해 전역 함수가 아닌 내부 함수를 호출한다.

 

함수 레벨 스코프

var 키워드로 선언된 변수는 오직 '함수'의 코드 블록, 즉 함수 몸체만을 지역 스코프로 인정한다.

즉, 함수가 아닌 다른 일반 코드 블록 (if 문, for 문 등)에서는 지역 스코프가 되지 않아 상위 스코프에서도 참조가 가능해진다.

 

렉시컬 스코프

var x = 1;

function foo() {
  var x = 10;
  bar();
}

function bar() {
  console.log(x);
}

foo();
bar();

위의 실행 결과는 bar의 상위 스코프가 무엇인지에 따라 결정된다.

 

1. 동적 스코프: 함수를 어디서 호출했는지에 따라 함수의 상위 스코프를 결정하는 경우

  ☞ bar의 상위 스코프: foo의 지역 스코프, 전역 스코프

2. 렉시컬 스코프 / 정적 스코프: 함수를 어디서 정의했는지에 따라 함수의 상위 스코프를 결정하는 경우

  ☞ bar의 상위 스코프: 전역 스코프

 

자바스크립트는 렉시컬 스코프를 따르므로, 함수가 호출된 위치는 상위 스코프의 결정에 아무런 영향을 주지 않는다.

따라서 함수의 정의가 실행될 때 자신의 상위 스코프를 기억하고, 함수가 호출될 때마다 기억해놓은 상위 스코프를 참조한다.

따라서 위의 경우 bar의 상위 스코프는 어디서 호출이 되었든 foo가 아닌 전역 스코프이기 때문에 1을 두 번 출력한다.


* 스터디 교재:

실행 컨텍스트: 23장, 스코프: 13장