코딩쌀롱

[코어 자바스크립트] 2. 실행 컨텍스트 본문

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

이브✱ 2020. 12. 18. 21:00

2-1 실행 컨텍스트란?

- 실행 컨텍스트: 실행할 코드에 제공할 환경 정보들을 모아놓은 객체

- 구성 방법: 전역공간(자동생성), eval함수(악마), 함수 실행

 

 동일한 환경에 있는 코드들을 실행할 때 필요한 환경 정보들을 모아 컨텍스트를 구성하고, 이를 콜 스택에 쌓아올렸다가, 가장 위에 쌓여있는 컨텍스트와 관련있는 코드들을 실행하고, 코드 실행이 종료되면 실행컨텍스트가 콜 스택에서 제거되고, 이전 컨텍스트를 다시 실행. 마지막으로는 전역 컨텍스트가 남고, 제거되고, 모든 코드 실행 종료.

전역 컨텍 → 컨텍1쌓임 → 컨텍2쌓임 → 컨텍3쌓임 → 3코드끝, 컨텍3 콜스택에서 제거 → 2코드끝, 컨텍2 콜스택에서 제거 → 1코드끝, 컨텍1 콜스택에서 제거 → 전역 컨텍 제거 → 빈 콜 스택, 종료

 

* 전역 컨텍스트: 처음 자바스크립트 코드를 실행하는 순간 전역 컨텍스트가 콜 스택에 담김.

(최상단의 공간은 별도 실행 명령 없어도 브라우저에서 자동으로 실행)

 

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

(기존의 컨텍스틑 새로 쌓인 컨텍스트보다 아래에 위치할 수 밖에 없기 때문)

 

Execution context Variable Environment 선언 시점의 L.E 스냅샷으로 변경사항 반영X, 먼저 생성됨
Lexical Environment 식별자 정보, 외부 환경 정보를 실시간 반영, V,E를 그대로 복사해서 활용
This Binding this로 지정된 객체

2-2 VariableEnvironment

VariableEnvironment에 담기는 내용은 LexicalEnvironment와 같지만 최초 실행 시의 스냅샷을 유지하고, 변경 사항이 반영되지 않는 다는 점에서 다르다. 실행 컨텍스트를 생성할 때 VariableEnvironment에 정보를 먼저 담은 다음, 이를 그대로 복사해서 LexicalEnvironment를 만들고, 이후에는 LexicalEnvironment를 주로 활용한다.

2-3 LexicalEnvironment

environmentRecord

현재 컨텍스트와 관련된 식별자 정보들이 저장 (매개변수 이름, 함수 선언, 변수명)

컨텍스트 내부 전체를 처음부터 끝까지 쭉 훑어 변수 정보를 순서대로 수집 (코드 실행 전 정보만 수집)

 

코드가 실행되기 전임에도 자바스크립트 엔진은 이미 해당 환경에 속한 변수명들을 모두 알게 되는 것!

== '자바스크립트 엔진은 식별자들을 최상단으로 끌어올려 놓은 다음 실제 코드를 실행한다.'

여기서 호이스팅 개념이 등장하게 된 것🤭

 

변수를 호이스팅할 때 선언부만 끌어올리고, 할당부는 그대로 두고 코드 실행할 때 실행한다.

함수 선언문은 함수 전체를 끌어올린다.(함수표현식과의 차이점)

 

- 함수 선언문: function 정의부만 존재, 별도의 할당 명령이 없는 것

                      함수 전체를 호이스팅 → 선언 전에 호출해도 실행, 큰 혼란의 원인

- 함수 표현식: 정의한 function을 별도의 변수에 할당하는 것

                      변수 선언부만 호이스팅 → 선언 전에 호출하면 에러

 

outerEnvironmentReference

현재 함수가 선언될 당시의 LexicalEnvironment 참조 복사 → [ 실행컨텍스트, { 식별자 정보 } ]

즉, 직전 실행 컨텍스트의 LexicalEnvironment를 참조한다.

 

각 outerEnvironmentReference는 오직 자신이 선언된 시점의 LexicalEnvironment만 참조하고 있으므로 가장 가까운 요소부터 차례대로 접근할 수 있다. 이런 구조적 특성 덕분에 여러 스코프에서 동일한 식별자를 선언한 경우에는 무조건 스코프 체인 상에서 가장 먼저 발견된 식별자에만 접근 가능하게 되는 것(변수은닉화)이다!👏🏼

그렇기 때문에 스코프 체인을 타고 접근 가능한 변수의 수는 늘어난다.

 

* 스코프: 식별자에 대한 유효범위 (전역공간을 제외하면 오직 함수에 의해서만 스코프가 생성)

* 스코프체인: 식별자의 유효범위를 안에서부터 바깥으로 차례로 검색해나가는 것

* 변수은닉화: variable shadowing, 스코프 체인 상 먼저 발견된 식별자에 접근, 먼 곳의 동일한 이름의 변수에는 접근하지 못하는 것

 

- 전역 변수: 전역 공간에서 선언한 변수, 코드의 안정성을 위해 가급적 전역변수 사용을 최소화

- 지역 변수: 함수 내부에서 선언한 변수

 

공부하면서 정리했던 것

Lexical Environment → environmentRecord(변수 호이스팅), outerEnvironmentReference(스코프 체인)

 식별자에 접근하려고 하면 현재 실행 컨텍스트이 L.E에서 식별자를 저장해놓은 environmentRecord에서 찾고, 만약에 없으면 outerEnvironmentReference에 가서 직전 컨텍스트의 environmentRecord에서 찾아본다. 없으면 또 outerEnvironmentReference에서 직전 컨텍스트의 environmentRecord를 계속 반복하면 전역 컨텍스트의 L.E로 가서 전역 변수를 참조할 수 있게 되고, 이렇게 점점 상위 컨텍스트로 가면서 식별자를 찾는 스코프 체인이 발생한다.

 

예시로 이해하기

매개변수와 변수에 대한 호이스팅

function a (x) {   // ①매개변수
   console.log(x); // (1)
   var x;          // ②변수 선언
   console.log(x); // (2)
   var x = 2;      // ③변수 선언
   console.log(x); // (3)
}
a(1);

(1), (2), (3)의 결과를 예상해보자.

(1) 인자로 받았으니까 1

(2) 선언하고 할당 안 해줬으니까 undefined

(3) 2를 할당해줬으니까 2

function a (x) {   
   var x = 1;      // ①매개변수 선언
   console.log(x); // (1)
   var x;          // ②변수 선언
   console.log(x); // (2)
   var x = 2;      // ③변수 선언
   console.log(x); // (3)
}
a(1);

인자와 함께 함수를 호출한 경우는, arguments에 전달된 인자를 담는 것을 제외하면 코드 내부에서 변수를 선언한 것과 같다. 즉, 인자를 함수 내부의 다른 코드보다 먼저 선언 및 할당이 이뤄진 것으로 간주할 수 있다. 

function a (x) {
   var x; // ①선언
   var x; // ②선언
   var x; // ③선언
   
   x = 1;          // ①할당
   console.log(x); // (1)
   console.log(x); // (2)
   x = 2;          // ③할당
   console.log(x); // (3)
}
a(1);

호이스팅 처리 과정을 코드로 표현해보았다. environmentRecord는 어떤 식별자들이 있는지에만 관심이 있고, 각 식별자에 어떤 값이 할당될 것인지는 관심이 없다. 따라서 변수를 호이스팅할 때 변수명만 끌어올리고 할당 과정은 원래 자리에 그대로 남겨둔다.

 

(1) 2, (2) undefined, (3) 2로 출력되리라 예상했는데, (1) 1, (2) 1, (3) 2라는 결과가 나왔다.

 

함수 선언의 호이스팅

function a () {
   console.log(b);  // (1)
   var b = 'bbb';   // 변수 선언
   console.log(b);  // (2)
   function b () {} // 함수 선언
   console.log(b);  // (3)
}
a();

결과를 예상해보면

(1) b의 값이 없으니 undefined

(2) 할당했으니까 'bbb'

(3) 함수 선언후니까 b함수 

function a () {
   var b;           // 변수 선언
   var b = function b () {} // 함수 선언
   //호이스팅이 끝난 함수선언문은 함수명으로 선언한 변수에 함수를 할당한 것처럼 여길 수 있다.
   
   console.log(b);  // (1)
   b = 'bbb';       // 변수 할당
   console.log(b);  // (2)
   console.log(b);  // (3)
}
a();

a 함수를 실행하는 순간 a 함수의 실행 컨텍스트 생성, 식별자 정보 수집.

변수는 선언부와 할당부를 나눠 선언부만 끌어올리고, 함수 선언은 함수 전체를 끌어올린다.

 

(1) undefined, (2) 'bbb', (3) b함수로 출력을 예상했지만, (1) b함수, (2)'bbb', (3) 'bbb'라는 결과가 나왔다.

 

 

 


정재남 작가님의 〔 코어 자바스크립트 〕 책을 공부하며 정리했습니다.

Comments