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

이예슬·2022년 12월 18일
0

실행 컨텍스트

실행 컨텍스트(execution context)는 실행할 코드에 제공할 환경 정보들을 모아놓은 객체로 자바스크립트의 동적 언어로서의 성격을 가장 잘 파악할 수 있는 개념이다. 자바스크립트는 어떤 실행 컨택스트가 활성화되는 시점에 선언된 변수를 위로 끌어올리고(호이스팅, hoisting), 외부 환경 정보를 구성하고, this 값을 설정하는 등의 동작을 수행한다.

💡 실행 컨텍스트: 실행할 코드에 제공할 환경 정보들을 모아놓은 객체
  • 소스코드를 실행하는 데 필요한 환경을 제공하고 코드의 실행 결과를 실제로 관리하는 영역
  • 식별자를 등록하고 관리하는 스코프와 코드 실행 순서 관리를 구현한 내부 메커니즘으로 모든 코드는 실행 컨텍스트를 통해 실행되고 관리된다.
  • 식별자와 스코프는 실행 컨텍스트의 LexicalEnvironment로 관리
  • 코드 실행 순서는 실행 컨텍스트 콜스택으로 관리

실행 컨텍스트란?

자바스크립트 엔진은 동일한 환경에 있는 코드들을 실행할 때 필요한 환경 정보들을 모아 컨텍스트를 구성하고 이를 콜 스택에 쌓아올렸다가 가장 위에 쌓여 있는 컨텍스트와 관련 있는 코드를 실행하는 방식으로 전체 코드와 환경의 순서를 보장한다.

이 때 실행 컨텍스트는 소스코드의 타입에 따라 생성 과정과 관리 내용이 달라지게 된다.

ECMAScript에서는 소스코드를 4가지 타입으로 구분하며 이 4가지 타입의 소스코드는 실행 컨텍스트를 생성한다.

전역 코드전역에 존재하는 소스코드, 전역에 정의된 함수, 클래스 등의 내부 코드는 포함되지 않음전역 코드는 전역으로 관리하기 위해 최상위 스코프인 전역 스코프를 생성해야 한다. 그리고 var 키워드로 선언된 전역 변수와 함수 선언문으로 정의된 전역 함수를 전역 객체의 프로퍼티와 메서드로 바인딩 하고 참조하기 위해 전역 객체와 연결되야 한다. 이를 위해 전역 코드가 평가되면 전역 실행 컨텍스트가 생성된다.
함수 코드함수 내부에 존재하는 소스코드, 함수 내부에 중첩된 함수, 클래스 등의 내부 코드는 포함되지 않는다.함수 코드는 지역 스코프를 생성하고 지역변수, 매개변수, arguments 객체를 관리그리고 생성한 지역 스코프를 전역 스코프에서 시작하는 스코프 체인의 일원으로 연결해야 한다.이를 위해 함수 코드가 평가되면 함수 실행 컨텍스트가 생성.
eval 코드빌트인 전역 함수인 eval 함수에 인수로 전달되어 실행되는 소스코드 eval 코드는 strict mode에서 자신만의 독자적인 스코프를 생성, 이를 위해 eval 코드가 평가되면 eval 실행 컨텍스트가 생성
모듈 코드모듈 내부에 존재하는 소스코드, 모듈 내부의 함수, 클래스 등의 내부 코드는 포함되지 않음 모듈 코드는 모듈별로 독립적인 모듈 스코프를 생성,이를 위해 모듈 코드가 평가되면 모듈 실행 컨텍스트 생성실행 컨텍스트는 실행할 코드에 제공할 환경 정보들을 모아놓은 객체이다. 즉 동일한 환경에 있는 코드들을 실행할 때 필요한 환경 정보들을 모아 컨텍스트를 구성하고 이를 콜 스택에 쌓아올렸다가 가장 위에 쌓여 있는 컨텍스트와 관련 있는 코드를 실행하는 식으로 전체 코드와 환경의 순서를 보장한다.

4가지 소스코드 중 우리가 흔히 실행 컨텍스트를 구성하는 방법은 함수를 실행하는 것이다.

var a = 1; 
function outer(){
  function inner(){
    console.log('inner 함수 안',a) //
    var a = 3;
  }
  inner()
  console.log('outer 함수 안',a) //
}
outer()
console.log('전역',a) //

최상단의 공간은 코드 내부에서 별도의 실행 명령이 없어도 브라우저에서 자동으로 실행하므로 자바스크립트 파일이 열리는 순간 전역 컨텍스트가 활성화된다고 이해하면 된다. 전역 컨텍스트와 관련된 코드가 실행되다가 함수를 만나면 해당 함수에 해당하는 컨텍스트가 콜 스택에 저장되고 전역 컨텍스트와 관련된 코드의 실행을 일시 중단하고 최상단에 있는 컨텍스트를 실행한다. 그 후 함수의 실행이 종료되면 해당 함수의 실행 컨텍스트가 콜 스택에서 제거되며 다시 그 아래에 있는 컨텍스트가 진행된다. 최종적으로 전역 공간에 더는 실행할 코드가 남아있지 않으면 전역 컨텍스트도 제거되고 콜 스택에는 아무것도 남지 않은 상태로 종료된다.

즉 실행 컨텍스트가 콜 스택의 맨 위에 쌓이는 순간이 곧 현재 실행할 코드에 관여하게 되는 시점이다.

자바스크립트 엔진은 실행 컨텍스트가 활성화될 때 해당 컨텍스트에 관련된 코드들을 실행하는 데 필요한 환경 정보들을 수집해서 실행 컨텍스트 객체에 저장한다.

이 때 저장되는 정보들은 아래와 같다.

  • VariableEnvironment: 현재 컨텍스트 내의 식별자들에 대한 정보 + 외부 환경 정보, 선언 시점의 LexicalEnvironment의 스냅샷(snapshot)으로 변경 사항은 반영되지 않음
  • LexicalEnvironment: 처음에는 Variable Environment와 같지만 변경 사항이 실시간으로 반영됨
  • ThisBinding: this 식별자가 바라봐야 할 대상 객체

VariableEnvironment

variableEnvironment에 담기는 내용은 LexicalEnvironment와 같지만 최초 실행시의 스냅샷을 유지한다는 점이 다르다.

실행 컨텍스트를 생성할 때 VariableEnvironment에 정보를 먼저 담은 다음 이를 복사하여 LexicalEnvironment를 만들고 이후에는 LexicalEnvironment를 주로 활용하게 된다.

VariableEnvironment와 LexicalEnvironment 내부에는 environmentRecord와 outer-EnvironmentReference로 구성되어 있다.

LexicalEnvironment

environmentRecord와 호이스팅

environmentRecord에는 현재 컨텍스트와 관련된 코드의 식별자 정보들이 저장된다.

컨텍스트를 구성하는 함수에 지정된 매개변수 식별자, 선언한 함수가 있을 경우 그 함수 자체, var로 선언된 변수의 식별자 등이 식별자에 해당한다. 컨텍스트 내부 전체를 처음부터 끝까지 쭉 훑어나가며 순서대로 수집한다.

참고 : 전역 실행 컨텍스트는 변수 객체를 생성하는 대신 자바스크립트 구동 환경이 별도로 제공하는 객체, 즉 전역 객체를 활용한다. 전역 객체에는 브라우저의 window, Node.js의 global 객체 등이 있다. 이들은 자바스크립트 내장 객체가 아닌 호스트 객체로 분류된다.

코드가 실행되기 전에 변수 정보를 수집하는 과정을 모두 마쳤으므로 자바스크립트 엔진은 이미 해당 환경에 속한 코드의 변수명들을 모두 알고 있게 된다. 호이스팅이란 자바스크립트 엔진이 식별자들을 최상단으로 끌어올려놓은 다음 실제 코드를 실행한다는 뜻이다.

호이스팅 규칙

environmentRecord에는 매개변수의 이름, 함수 선언, 변수명 등이 담긴다.

function a(x) {
	console.log(x) 
	var x
	console.log(x) 
	var x = 2
	console.log(x) 
} 
a(1) 

변수는 선언부와 할당부를 나누어 선언부만 끌어올리는 반면 함수 선언은 함수 전체를 끌어올린다.

함수 선언문과 함수 표현식

함수 선언문은 function의 정의부만 존재하고 별도의 할당 명령이 없는 것을 의미한다.

함수 표현식은 정의한 function을 별도의 변수에 할당하는 것을 말한다.

function a() {...} // 함수 선언식
a()

var b = function(){...) // 익명 함수 표현식
b()

var c = function d () {...} // 기명 함수 표현식 
c()
d() //❗️에러 발생

💡기명 함수 표현식은 외부에서는 함수명으로 함수를 호출할 수 없다. 과거에는 디버깅시 어떤 함수인지를 추적하기 위해 사용했지만 현재는 모든 브라우저들이 익명 함수 표현식의 변수명을 함수의 name 프로퍼티에 할당하므로 현재는 잘 쓰이지 않는다.

console.log(sum(1, 2)) 
console.log(multiply(3, 4)) 

function sum(a, b) {
	return a + b 
} 

var multiply = function(a, b){
	return a * b
} 

위 코드에서 함수 선언문은 전체를 호이스팅하고 변수는 선언부만 끌어올린다. 즉 sum 함수는 선언전에 호출해도 호이스팅에 의해 아무 문제 없이 실행되지만 multiply는 선언부만 끌어올려져 있으므로 선언전에 호출할 경우 multiply is not a function이라는 에러 메시지가 출력된다.

함수 선언문은 전체 코드의 유지 보수성을 저하시킨다. 만약 sum이라는 함수가 선언된지 모른채 다시 sum이라는 함수를 생성하면 이 전에 작성된 코드에도 새로 생성된 함수가 영향을 끼치기 때문이다.

전역 컨텍스트가 활성화될 때 전역 공간에 선언된 함수들이 모두 가장 위로 끌어올려진다. 동일한 변수명에 서로 다른 값을 할당할 경우 나중에 할당한 값이 먼저 할당한 값을 덮어씌운다.(override) 따라서 코드를 실행하는 중에 실제로 호출되는 함수는 오직 마지막에 할당된 함수이다. 즉 개발자의 의도대로 실행되지 않을 수 있다.

호이스팅 우선순위

// hoisting 우선 순위 
var hi = 'hi'

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

console.log('어떤 hi가 출력될까요?',hi)

자바스크립트 파서는 함수보다 변수를 더 우위로 판단하여 최상단에 위치시킨다.

스코프, 스코프 체인, outerEnvironmentReference

스코프란 식별자에 대한 유효범위이다. 어떤 경계 A의 외부에서 선언한 변수는 A의 외부뿐 아니라 A의 내부에서도 접근이 가능하지만, A의 내부에서 선언한 변수는 오직 A의 내부에서만 접근할 수 있다.

ES5까지의 자바스크립트는 전역 공간을 제외하면 오직 함수에 의해서만 스코프가 생성되었지만 ES6이후로는 블록에 의해서도 스코프 경계가 발생된다. 단 블록은 var로 선언한 변수에 대해서는 적용하지 않고 오직 새로 생긴 let, const, class, strict mode에서의 함수 선언 등에 대해서만 범위로소의 역할을 수행한다.

식별자의 유효범위를 안에서부터 바깥으로 차례로 검색해나가는 것을 스코프 체인(scope chain)이라고 하며 이를 가능케 하는 것이 outerEnvironmentRefenence이다.

스코프 체인

  • 스코프가 계층적으로 연결된 것
    • 함수 내부에 함수가 있는 형태 → 함수의 중첩

    • 함수가 중첩될 경우 함수의 지역 스코프도 중첩

      💡스코프 체인은 스코프가 함수의 중첩에 의해 계층적인 구조를 가지는 것을 말한다. 

  • 변수를 참조할 때 자바스크립트 엔진은 스코프 체인을 통해 변수를 참조하는 코드의 스코프에서 시작하여 상위 스코프 방향으로 이동하며 선언된 변수를 검색한다.

outerEnvironmentReference는 현재 호출된 함수가 선언될 당시의 LexicalEnvironment를 참조한다. 선언이 일어날 수 있는 시점이란 콜 스택 상에서 어떤 실행 컨텍스트가 활성화된 상태일 뿐이다. 모든 코드는 실행 컨텍스트가 활성화 상태일 때 실행되기 때문이다.

예를 들어 A 함수 내부에 B 함수를 선언하고 B 함수 내부에 C 함수를 선언한 경우 C의 outerEnvironmentReference는 함수 B의 lexicalEnvironment를 참조한다. 함수 B의 lexicalEnvironment에 있는 outerEnvironment는 다시 함수 B가 선언되던 때의 lexicalEnvironment를 참조한다. 이처럼 outerEnvrionmentReference는 연결리스트(linked lsit)의 형태를 띄며 참조를 계속 타고 올라가다 보면 마지막엔 전역 컨텍스트의 lexicalEnvironment가 있을 것이다. 또한 각 outerEnvironmentRereference는 오직 자신이 선언된 시점의 lexicalEnvironment만 참조하고 있으므로 가장 가까운 요소부터 차례대로만 접근할 수 있고 다른 순서로 접근하는 것은 불가능하다. 이러한 구조적 특성 덕분에 여러 스코프에서 동일한 식별자를 선언한 경우에는 무조건 스코프 체인 상에서 가장 먼저 발견된 식별자에만 접근 가능하게 된다.

예를 들어 실행되고 있는 함수 안에 a 라는 변수가 선언되어 있다면 해당 함수 실행 시점 이전에 a라고 선언된 변수가 있어도 스코프 체인 검색을 진행하지 않으므로 함수 안에 있는 a를 반환한다. 이를 변수 은닉화(variable shadowing)이라고 한다.

전역 변수와 지역 변수

전역 공간에서 선언한 변수는 전역변수이고 함수 내부에서 선언한 변수는 무조건 지역 변수이다.

코드의 안전성을 위해 가급적 전역 변수 사용을 최소화하고자 해야 한다.


<코어 자바스크립트> 정재남, 위키북스(2019)

profile
꾸준히 열심히!

0개의 댓글