본문 바로가기
자바스크립트/코어 자바스크립트

코어자바스크립트 2장<실행 컨텍스트>

by SONBAEJUN 2022. 8. 12.

1. 실행 컨텍스트란?

1-1) 실행 컨텍스트

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

1-2) 스택과 큐

 

  • 스택 : 출입구가 하나뿐인 깊은 우물 같은 데이터 구조(LIFO)
  • 큐 : 양쪽이 모두 열려있는 파이프(하지만 입, 출구는 하나) (FIFO)
  • 동일한 환경에 있는 코드들을 실행할 때 필요한 환경 정보들을 모아 컨텍스트를 구성하고, 이를 콜 스택에 쌓는다.

  • 위 그림에서 (1)번구간이 실행될 때 (처음 자바스크립트를 실행) 전역 컨텍스트를 콜 스택에 담는다
  • 그 뒤 계속 실행하다가 (3)에서 outer()함수를 호출하면 outer에 대한 환경정보를 수집해 outer 실행컨텍스트를 생성 후 콜 스택에 담는다, 이때 콜 스택 맨 위에 outer 실행 컨텍스트가 놓인 상태가 되었으므로 전역 컨텍스트 관련 코드의 실행을 일시 중단하고 대신 outer 함수 내부의 코드들을 순차로 실행합니다.
  • 다시 (2)에서 inner() 함수의 실행 컨텍스트가 콜 스택의 가장 위에 담기면 outer 컨텍스트와 관련된 코드의 실행을 중단하고 inner 함수 내부의 코드를 순서대로 진행한다.

 

  • 스택 구조를 잘 생각해보면 한 실행 컨텍스트가 콜 스택의 맨 위에 쌓이는 순간이 곧 현재 실행할 코드에 관여하게 되는 시점이다
  • 어떤 실행 컨텍스트가 활성화될 때 JS 엔진은 해당 컨텍스트에 관련된 코드들을 실행하는데 필요한 환경 정보들을 수집해서 실행 컨텍스트 객체에 지정하게 된다.
  • 이 객체는 JS엔진이 활용할 목적으로 생성될 뿐, 사용자가 확인할 수는 없고, 담기는 정보는 아래 그림과 같다.

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

2. VariableEnvironment

  • VariableEnvironment는 현재 컨텍스트 내의 식별자들에 대한 정보 + 외부 환경 정보, 선언 시점의 LexicalEnvironment의 스냅샷으로, 변경 사항이 반영되지 않는다.
  • VariableEnvironment에 담기는 내용은 LexicalEnvironment와 같지만 최초 실행 시의 스냅샷을 유지한다는 점이 다름
  • 실행 컨텍스트를 생성할 때 VariableEnvironment에 정보를 먼저 담고, LexicalEnvironment에 그대로 정보를 복사한 뒤, 주로 LexicalEnvironment를 활용한다.
  • VariableEnvironment과 LexicalEnvironment의 내부는 environmentRecord와 outer-EnvironmentReference로 구성

3. LexicalEnvironment

  • LexicalEnvironment는 '어휘적 환경', '정적 환경' 등으로 불리우나, '사전적 환경'으로 이해하는 것도 좋을드하다

3-1) environmentRecord와 호이스팅

  • environmentRecord에는 현재 컨텍스트와 관련된 코드의 식별자 정보들이 저장된다. 컨텍스트 내부 전체를 처음부터 끝까지 쭉 훑어나가며 순서대로 수집한다. 따라서 코드를 실행하기 전에 모든 변수명들을 알고있는 것이다.
  • '자바스크립트 엔진은 식별자들을 최상단으로 끌어올려놓은 다음 실제 코드를 실행한다'라고 간주할 수 있고, 이를 '호이스팅'으로 정의한다.

호이스팅의 개념을 이해하기 위해 예제코드를 통해 살펴보자.

  • 호이스팅의 개념이 없을 땐, (1)에선 함수 호출 시 전달한 1이 출력되고, (2)에선 x에 할당값이 없으므로 undefined (3)에선 2가 출력될 것이라 예상된다.

  • 호이스팅을 마치면 다음과 같은 코드와 같다고 볼 수 있다.
  • 여기서 핵심은, 호이스팅은 코드 내 어떤 식별자들이 있는지에만 집중하지, 그 식별자에 어떤 값이 할당될 것인지에는 관심이 없다. 따라서 변수를 호이스팅할땐 변수명만 끌어올리고 할당 과정은 원래자리에 그대로 둔다
  • 따라서 위와 같이 'var x' 변수 선언은 호이스팅되어 위로 올라가고 데이터 할당은 그대로 남아있는 것이다.
  • 따라서 JS에서 실제 출력은 (1),(2) 모두 1, (3)에선 2가 출력된 뒤 실행 컨텍스트가 콜 스택에서 제거된다.

3-2) 함수 선언문과 함수 표현식

  • 함수 선언문 : function 정의부만 존재하고 별도의 할당 명령이 없는 것(반드시 함수명이 정의돼야함)
  • 함수 표현식 : 정의한 function을 별도의 변수에 할당하는 것(함수명이 정의되지 않아도 상관없다(익명))
  • 기명 함수 표현식 : 함수명을 정의한 함수 표현식, 외부에서 함수명으로 함수를 호출 할 수 없다. 함수 내부에서만 됨
function a() { /* ... */ }  //함수 선언문
a(); //실행OK

var b = function() {/* ... */}  //(익명) 함수 표현식. 변수명 b가 곧 함수명
b(); //실행OK

var c = function d() {/* ... */}  //(기명) 함수 표현식. 변수명은 c, 함수명은 d
c(); //실행OK
d(); //에러 !

 

※함수 표현식 사용 시 변수명만 호이스팅 되고 함수 부분은 그대로 그 자리에 남기 때문에 에러 발생의 원인이 된다. 이를 예제를 통해 이해해보자.

위 코드에서 호이스팅을 진행해보자

위와 같이 함수 선언문은 함수 전체를 호이스팅하지만

함수 표현식은 변수의 선언부만을 호이스팅하게 되고, 변수의 할당부는 원래 자리에 남게된다!!

따라서 6번줄 실행 시 var multiply만 선언됐지 함수의 할당은 이뤄지지 않았으므로 'multiply is not a function' error가 발생!

위 특성때문에 함수 표현식은 이미 선언된 함수를 재정의 할때에 용이하다.(재정의 한 줄 이후부터 그 내용이 반영되므로)

(만약 함수 선언문을 통해 재정의를 한다면 함수 전체가 호이스팅 되므로 나중에 정의한 내용으로 덮어씌워 짐)

 

3-3) 스코프, 스코프 체인, outerEnvironmentReference

  • 스코프 : 식별자에 대한 유효범위 --> 어떤 경계 A의 외부에서 선언한 변수는 A의 외부와 내부 모두 접근가능하지만 A의 내부에서 선언한 변수는 A의 내부에서만 접근이 가능하다는 개념
  • 이 스코프는 ES5까지의 JS에선 전역공간을 제외하면 오직 함수에서만 스코프가 생성됐음.
  • 스코프를 안에서부터 바깥으로 차례로 검색해 나가는 것을 '스코프 체인'이라고 한다.
  • 그리고 스코프 체인을 가능케 하는것이 LexicalEnvironment의 두번째 수집자료인 outerEnvironment
  • 여러 스코프에서 동일한 식별자를 선언한 경우에는 무조건 스코프 체인 상에서 가장 먼저 발견된 식별자에만 접근 가능하다.

  • 위 코드에서도 inner()함수는 스코프에 의해 inner함수, outer함수, 전역함수를 참조할 수 있다.
  • 스코프는 안에서부터 바깥으로 차례대로 검색해 나간다 했고, 스코프 체인상에서 가장 먼저 발견된 식별자에만 접근한다.
  • 따라서 inner 함수 내부에 있는 a가 가장 먼저 발견되므로 a 식별자는 이것이 쓰이고 4번째 줄에선 undefined가 출력된다.
  • 반면 outer 함수는 내부의 inner함수를 참조할 수 없고, outer, 전역함수 순으로 참조한다.
  • 따라서 outer 내부엔 a가 없으므로 전역함수에 있는 a를 사용하게 되고, 8번째 줄의 a는 1이 출력되게 된다.
  • 마찬가지로 11번째 줄의 a는 전역 컨텍스트이므로 전역 컨텍스트만을 참조하고, 따라서 a는 1이 출력된다.

위 코드 실행시의 스코프 체인 표

위와 같은 특성을 통해 변수 은닉화가 가능하다

 

3-4) 전역변수와 지역변수

  • 전역 변수 = 전역 스코프에서 선언한 수이며, 어디서든지 접근이 가능하다
  • 지역 변수 = 함수 내부에서 선언한 변수이며, 함수 내부에서만 접근이 가능하기에 코드의 안정성 향상
  • 전역 변수 사용 시 코드의 혼선과 원치 않는 혼란을 맞을 가능성이 크기에 전역 변수의 사용을 최소화 하는 것이 좋다

4. this

  • 실행 컨텍스트의 thisBinding에는 this로 지정된 객체가 저장된다.
  • 실행 컨텍스트 활성화 당시에 this가 지정되지 않은 경우 this에는 전역 객체가 저장된다.
  • 그 밖에는 함수를 호출하는 방법에 따라 this에 저장되는 대상이 다르다.