TIL.실행컨텍스트

chloe·2020년 12월 30일
0

TIL

목록 보기
26/81
post-thumbnail
post-custom-banner

📍실행 컨텍스트?

실행할 코드에 제공할 환경 정보들을 모아놓은 객체
자바스크립트의 동적 언어 성격을 가장 잘 파악할 수 있는 개념이다.
실행컨텍스트는 실행 가능한 코드가 실행되기위해 필요한 환경이라고 볼 수 있다.

이에 앞서 스택의 개념을 알아야 할 필요가 있다.

스택:출입구가 하나뿐인 깊은 우물과 같은 데이터 구조, 입력과 출력이 한 방향
큐: 양쪽이 모두 열려있는 파이프를 떠올리면 된다. 입력과 출력을 한 쪽 끝으로 제한함

흔히 실행 컨텍스트를 구성하는 방법은 함수를 실행하는 것이다.

var a=1;
function outer(){
  function inner(){
    console.log(a);
    var a=3;
  }
  inner();
  console.log(a);
}
outer();
console.log(a);

위 함수를 아래 그림으로 해석해보자!

  1. 처음 자바스크립트 코드를 실행하면 전역 컨텍스트가 콜 스택에 담긴다. (즉, 자바스크립트 파일이 열리는 순간 전역 켄텍스트가 활성화된다)

  2. outer함수를 호출하면 자바스크립트 엔진은 outer에 대한 환경 정보를 수집해서 outer 실행 컨텍스트를 생성한 후 콜 스택에 담는다.

  3. 콜 스택의 맨 위에 outer 실행 컨텍스트가 있으므로 전역 컨텍스트와 관련된 코드의 실행은 일시중단하고 outer실행 컨텍스트와 관련된 코드 즉, outer 함수 내부의 코드들을 순차적으로 실행한다.

  4. inner함수의 실행 컨텍스트가 콜 스택의 가장 위에 담기면 outer컨텍스트와 관련된 코드의 실행은 중단되고 inner함수내부의 코드를 순서대로 진행한다.

  5. inner함수 내부에서 a 변수의 값을 출력하고 나면 inner함수의 실행이 종료되면서 inner 실행 컨텍스트가 콜 스택에서 제거된다.

  6. outer 컨텍스트가 콜 스택의 맨 위에 존재하게 되고 a 변수 값을 출력하고 나면 outer함수의 실행이 종료되어 outer 실행 컨텍스트가 콜 스택에서 제거된다.

  7. 맨 마지막, a변수 값을 출력하고 나면 전역 공간에 더는 실행할 코드가 남아있지 않아 전역 컨텍스트가 제거되고 콜스택에는 아무것도 남지 않는다.


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

🧐 활성화된 실행 컨텍스트의 수집 정보는 아래와 같다.
(VariableEnvironment,LexicalEnvironment,ThisBinding)

VariableEnvironment

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

실행 컨텍스트를 생성할 때,

1)VariableEnvironment에 정보를 먼저 담은 다음,
2)이를그대로 복사해서 LexicalEnvironment를 만들고
3)이후에는 LexicalEnvironment를 주로 활용하게 된다.

VariableEnvironment와 LexicalEnvironment의 내부는 매개변수명, 변수의 식별자, 선언한 함수의 함수명 등을 수집하는environmentRecord와 바로 직전 컨텍스트의 LexicalEnvironment를 참조하는 outer-environmentReference로 구성돼있다.

  • 실행 컨텍스트 객체는 활성화되는 시점에서 VariableEnvironment,LexicalEnvironment,ThisBinding 세 가지 정보를 수집한다.
  • 실행 컨텍스트를 생성할 때는 VariableEnvironmentLexicalEnvironment가 동일한 내용으로 구성되지만 LexicalEnvironment의 경우 함수 실행 도중에 변경되는 사항이 즉시 반영되는 반면 VariableEnvironment는 초기상태를 유지한다.

LexicalEnvironment

어휘적 환경, 정적인 환경이라고도 하지만 사전적인이라는 의미가 잘 어울린다.
=> 코어자바스크립트에서는 다음과 같은 예를 든다.
현재 컨택스트의 내부에는 a,b,c와 같은 식별자들이 있고 그 외부 정보는 D를 참조하도록 구성돼있다. 컨텍스트를 구성하는 환경 정보들을 사전에서 접하는 느낌으로 모아놓은 것.

EnvironmentRecord와 호이스팅

environmentRecord에는 현재 컨텍스트와 관련된 코드의 식별자 정보들이 저장된다.
예를 들면, 컨텍스트를 구성하는 함수에 지정된 매개변수 식별자, 선언한 함수가 있을 경우 그 함수 자체, var로 선언된 변수의 식별자 등이 식별자에 해당한다.

코드가 실행되기 전에 자바스크립트 엔진은 이미 해당 환경에 속한 코드의 변수명들을 모두 알고 있다.그럼 자바스크립트 엔진은 식별자들을 최상단으로 끌어올려놓은 다음 실제 코드를 실행한다고 볼 수 있다. 여기서 호이스팅이라는 개념이 등장한다!

코드를 한번 살펴보자!

function a(){
  console.log(b); //1
  var b ='bbb';
  console.log(b);//2
  function b (){}
  console.log(b);//3
}
a();

a함수를 실행하는 순간 a 함수의 실행 컨텍스트가 생성된다. 이때 변수명과 함수 선언의 정보를 위로 끌어올린다(수집한다)

호이스팅을 마친 상태의 코드를 다시 봐보자!

function a(){
 var b;// 수집대상1. 변수는 선언부만 끌어올린다.
 function b(){}// 수집대상2.함수선언은 함수 전체를 끌어올린다.
  =>var b= function b(){}
  // 호이스팅이 끝난 상태의 함수 선언문은 함수명으로 선언한 변수에 함수를 할당한 것처럼 여길 수 있기에 이처럼 바꿀 수 있음
  console.log(b);//1
  b="bbb";// 변수의 할당부는 원래 자리에 남겨둔다.
  console.log(b);//2
  console.log(b);//3
}
a();
  1. 변수 b를 선언한다. 이때 메모리에서는 저장할 공간을 미리 확보하고, 확보한 공간의 주솟값을 변수 b에 연결해둔다.

  2. 다시 변수 b를 선언하고 함수 b를 선언된 변수 b에 할당하라? 그렇지만 이미 선언된 변수b가 있으므로 선언과정은 무시한다. 함수는 별도의 메모리에 담길 것이고 그 함수가 저장된 주솟값을 b와 연결된 공간에 저장할 것이다. 이제 변수 b는 함수를 가리키게 된다.

  3. 변수 b에 할당된 함수 b를 출력한다.

  4. 변수 b에 'bbb'를 할당하라? b와 연결된 메모리 공간에는 함수가 저장된 주솟값이 담겨 있었는데 이걸 문자열 'bbb'가 담긴 주솟값으로 덮어쓴다. 이제 변수 b는 문자열 'bbb'를 가리키게 됨

  5. 위 함수에서 2,3부분 모두가 'bbb'를 출력하고 이제 함수 내부의 모든 코드가 실행됐으니까 실행 컨텍스트가 콜스택에서 제거된다.


🎈함수 선언문과 함수 표현식

함수 선언문: function 정의부만 존재하고 별도의 할당 명령이 없는 것을 의미
반드시 함수명이 정의돼 있어야 한다
함수 표현식: 정의한 function을 별도의 변수에 할당하는 것을 의미
함수명 없어도 된다,익명 함수 표현식

function a(){}//함수 선언문. 함수명 a가 곧 변수명
var b =function ()//익명 함수 표현식, 변수명 b가 곧 함수명

함수 선언문과 함수 표현식의 실질적인 차이를 한편 살펴보자!

실행컨텍스트의 lexicalEnviroment는 두 가지 정보를 수집하는데, 여기서는 그중에서 enviromentRecord의 정보 수집과정에서 발생하는 호이스팅을 살펴보는 중이다. 호이스팅을 마친 아래 코드를 봐보자!

var sum=function sum(a,b){//함수 선언문은 전체를 호이스팅한다.
  return a+b;
};
var multiply;// 변수는 선언부만 끌어올린다
console.log(sum(1,2));
console.log(multiply(3,4));
multiply=function(a,b){// 변수의 할당부는 원래 자리에 남겨둔다.
  return a*b;
};

위 코드에서 보면 알 수 있듯이, 함수 선언문은 전체를 호이스팅하고 함수 표현식은 변수 선언부만 호이스팅했다. 함수를 다른 변수에 값으로써 할당한 것이 곧 함수 표현식이다. 여기서 함수 선언문과 함수표현식의 극적인 차이가 발생!!

  1. 메모리 공간을 확보, 확보된 공간의 주솟값을 변수 sum에 연결한다.

  2. 또 다른 메모리 공간을 확보하고 그 공간의 주솟값을 변수 multiply에 연결한다.

  3. sum 함수를 또 다른 메모리 공간에 저장하고 그 주솟값을 앞서 선언한 변수 sum의 공간에 할당한다. 변수 sum은 함수 sum을 바라보는 상태가 된다.

  4. sum을 실행한다. 정상적으로 실행되어 3이 나온다.

  5. console.log(multiply(3,4))부분을 보면 multiply에는 현재 값이 할당돼 있지 않기에 에러 메시지가 나올 것이다. 뒤 코드는 실행되지 않은 채 런타임 종료된다.

📍스코프, 스코프 체인?

스코프: 식별자에 대한 유효범위
스코프 체인: 식별자의 유효범위를 안에서부터 바깥으로 차례로 검색해나가는 것( 이를 가능하게 하는 것은 바로 LexicalEnvironment의 두 번째 수집 자료인 outerEnvironmentReference.

👉 꼭알고 가기: 어떤 함수를 선언(정의)하는 행위 자체도 하나의 코드에 지나지 않고 모든 코드는 실행 컨텍스트가 활성화될 때 실행이 된다!!

var a=1;//1
var outer=function(){//2
  var inner= function(){//3
    console.log(a);//4
    var a=3;//5
  }//6
  inner();//7
  console.log(a);//8
}//9
outer();//10
console.log(a);//11

위에 작성된 코드를 좀 더 구체적으로 살펴보자.

  1. 전역 컨텍스트가 활성화된다. 전역컨텍스트의 environmentRecord에 {a, outer} 식별자를 저장한다. 전역컨텍스트는 선언 시점이 없으니 전역 컨텍스트의 outerEnvironmentReference에는 아무것도 담기지 않는다.

  2. 전역 스코프에 있는 변수 a에 1을, outer에 함수를 할당한다.

  3. 10번째 줄에 있는 outer 함수를 호출한다. 전역 컨텍스트의 코드는 10번째 줄에서 임시 중단되고 outer 실행 컨텍스트가 활성화되서 2번째 줄로 이동한다.

  4. outer 실행 컨텍스트의 environmentRecord에 {inner}식별자를 저장한다. outerEnvironment에는 outer함수가 선언될 당시의 LexicalEnvironment가 담긴다. outer 함수는 전역공간에서 선언됐으므로 전역컨텍스트의 lexicalEnvironment를 참조복사한다.[Global,{a,outer}]라고 표기한다. 첫번째는 실행 컨텍스트의 이름, 두 번째는 environmentRecord객체이다.

  5. outer 스코프에 있는 변수 inner에 함수를 할당한다.

  6. inner함수를 호출한다. 이에 따라 outer 실행 컨텍스트 코드는 7번째 줄에서 임시 중단되고 inner 실행 컨텍스트가 활성화되어 3번째 줄로 이동한다.

  7. inner 실행 컨텍스트의 environmentRecord에 {a}식별자를 저장한다.
    outerEnvironment에는 inner함수가 선언될 당시의 LexicalEnvironment가 담긴다. inner함수는 outer함수 내부에서 선언됐으므로 outer함수의 LexicalEnvironment, 즉[outer,{inner}]를 참조복사한다.

  8. 4번째 줄, 식별자 a에 접근한다. 현재 활성화 상태인 inner 컨텍스트의 environmentRecord에서 a를 검색한다. a가 발견됐지만 아직 할당된 값이 없으므로 (undefined)출력

  9. inner 스코프에 있는 변수 a에 3을 할당한다.

  10. 6번째 줄에서 inner함수 실행이 종료된다. inner실행 컨텍스트가 콜 스택에서 제거 되고 바로 아래의 outer 실행 컨텍스트가 다시 활성화되면서 앞서 중단했던 7번째 줄의 다음으로 이동한다.

  11. 8번째 줄, 식별자a에 접근하고자 한다. 이때 자바스크립트 엔진은 활성화된 실행 컨텍스트의 LexicalEnvironment에 접근한다. 첫 요소의 environmentRecord에 a가 있는지 찾아보고 없으면 outerEnvironmentReference에 있는 environmentRecord로 넘어가는 식으로 계속해서 검색한다.
    여기서는 전역 LexicalEnvironment에 a가 있으니 그 a에 저장된 값 1을 반환한다.

  12. 9번째 줄에서 outer함수 실행이 종료된다. outer 실행 컨텍스트가 콜 스택에서 제거되고 바로 아래의 전역 컨텍스트가 다시활성화된다.

  13. 맨마지막 줄에서 식별자 a에 접근한다. 현재 활성화 상태인 전역 컨텍스트의 environmentRecord에서 a를 검색한다. 1이 출력된다. 모든 코드의 실행이 완료되고 전역컨텍스트가 콜스택에서 제거되고 종료된다.

위 예제의 전역변수: 전역 스코프에서 선언한 a와 outer
지역변수: outer함수 내부에서 선언한 inner,inner함수 내부에서 선언한 a.

  • 코드의 안정성을 위해 가급적 전역변수 사용을 최소화하는 것이 좋다.

참고: 코어자바스크립트

profile
Front-end Developer 👩🏻‍💻
post-custom-banner

0개의 댓글