실행컨텍스트(스코프, 변수, 객체, 호이스팅)

post-thumbnail

실행컨텍스트(스코프, 변수, 객체, 호이스팅)

자바스크립트의 **실행 컨텍스트**는 실행할 코드에 제공할 환경 정보들을 모아놓은 객체자바스크립트는 어떤 실행 컨텍스트가 활성화되는 시점에 다음과 같은 일을 함

  1. 선언된 변수를 위로 끌어올림 = 호이스팅(hoisting)
  2. 외부 환경 정보를 구성.
  3. this 값을 설정.

이런 현상들 때문에 JS에서는 다른 언어랑은 다른 특징들이 나타남.

(1) 실행 컨텍스트란?

실행 컨텍스트를 이해하기 위해서는, 콜 스택에 대한 이해가 반드시 필요. 자, 그 전에 “스택”이라는 개념에 대해서 먼저 이해를 해야함.

  • 스택 vs 큐 (이미지 출처 : [https://velog.io/@leejuhwan/스택STACK과-큐QUEUE](https://velog.io/@leejuhwan/%EC%8A%A4%ED%83%9DSTACK%EA%B3%BC-%ED%81%90QUEUE)) (이미지 출처 : https://velog.io/@leejuhwan/스택STACK과-큐QUEUE)
  • 콜 스택(call stack) 실행 컨텍스트란 실행할 코드에 제공할 환경 정보들을 모아놓은 객체 그 객체. 즉, 동일 환경에 있는 코드를 실행할 때 필요한 환경 정보들을 모아 컨텍스트를 구성하고 이것을 위에서 설명드린 ‘스택’의 한 종류인 콜스택에 쌓아올림. 가장 위에 쌓여있는 컨텍스트와 관련된 코드를 실행하는 방법으로 코드의 환경 및 순서를 보장할 수 있음.
    1. 컨텍스트의 구성
      1. 구성방법(여러가지가 있지만, 사실 함수만 생각하면 됨 👍)

        1. 전역공간
        2. eval()함수
        3. 함수(우리가 흔히 실행컨텍스트를 구성하는 방법)
      2. 실행컨텍스트 구성 예시 코드

        // ---- 1번
        var a = 1;
        function outer() {
        	function inner() {
        		console.log(a); //undefined
        		var a = 3;
        	}
        	inner(); // ---- 2번
        	console.log(a);
        }
        outer(); // ---- 3번
        console.log(a);
      3. 실행컨텍스트 구성 순서

        위 코드는 아래 순서로 진행이 됨
        (콜 스택에 쌓이는 실행컨텍스트에 의해 순서가 보장!)

        코드실행 → 전역(in) → 전역(중단) + outer(in) → outer(중단) + inner(in) → inner(out) + outer(재개) → outer(out) + 전역(재개) → 전역(out) → 코드종료

      4. 결국은 특정 실행 컨텍스트가 생성되는(또는 활성화되는) 시점이 콜 스택의 맨 위에 쌓이는(노출되는) 순간을 의미. 곧, 현재 실행할 코드에 해당 실행 컨텍스트가 관여하게 되는 시점을 의미한다고 받아들여주면 정확! 👍👍

  • 실행 컨텍스트 객체의 실체(=담기는 정보)
1. VariableEnvironment
    1. 현재 컨텍스트 내의 식별자 정보(=record)를 갖고있음.
        1. **`var a = 3`**
        2. 위의 경우, **`var a`**를 의미
    2. 외부 환경 정보(=outer)를 갖고있음.
    3. 선언 시점 LexicalEnvironment의 **snapshot**
2. LexicalEnvironment
    1. VariableEnvironment와 동일하지만, 변경사항을 실시간으로 반영.
3. ThisBinding
    1. this 식별자가 바라봐야할 객체

(2) VariableEnvironment, LexicalEnvironment의 개요

  • VE vs LE 이 두가지는 담기는 항목은 완벽하게 동일. 그러나, 스냅샷 유지여부는 다음과 같이 다름.
    1. VE : 스냅샷을 유지.

    2. LE : 스냅샷을 유지하지 않아요. 즉, 실시간으로 변경사항을 계속해서 반영

      결국, 실행 컨텍스트를 생성할 때, VE에 정보를 먼저 담은 다음, 이를 그대로 복사해서 LE를 만들고 이후에는 주로 LE를 활용

  • 구성 요소(VE, LE 서로 같음)
    1. VE, LE모두 동일하며, ‘environmentRecord’와 ‘outerEnvironmentReference’로 구성
    2. environmentRecord(=record)
      1. 현재 컨텍스트와 관련된 코드의 식별자 정보들이 저장.
      2. 함수에 지정된 매개변수 식별자, 함수자체, var로 선언된 변수 식별자 등
    3. outerEnvironmentReference(=outer)

(3) LexicalEnvironment(1) - environmentRocord(=record)와 호이스팅

  • 개요

    1. 현재 컨텍스트와 관련된 코드의 식별자 정보들이 저장(수집) 기록된다라고 이해해보면, record라는 말과 일맥상통

    2. 수집 대상 정보 : 함수에 지정된 매개변수 식별자, 함수 자체, var로 선언된 변수 식별자 등

    3. 컨텍스트 내부를 처음부터 끝까지 순서대로 훑어가며 수집

      💡 순서대로 **수집**한다고 했지, 코드가 **실행**된다고 하지는 않음!
  • 호이스팅

    1. 변수정보 수집을 모두 마쳤더라도 아직 실행 컨텍스트가 관여할 코드는 실행 전의 상태(JS 엔진은 코드 실행 전 이미 모든 변수정보를 알고 있는 것)

    2. 변수 정보 수집 과정을 이해하기 쉽게 설명한 ‘가상 개념’

      💡 가상개념이라는 말은, 실제로는 그렇진 않더라도 사람이 이해하기 쉬운 말로 풀어 표현했다는 것을 의미 😄
  • 호이스팅 규칙

    1. 호이스팅 법칙 1 : 매개변수 및 변수는 선언부를 호이스팅

      <적용 전>

      //action point 1 : 매개변수 다시 쓰기(JS 엔진은 똑같이 이해한다)
      //action point 2 : 결과 예상하기
      //action point 3 : hoisting 적용해본 후 결과를 다시 예상해보기
      
      function a (x) {
      	console.log(x);
      	var x;
      	console.log(x);
      	var x = 2;
      	console.log(x);
      }
      a(1);

      <매개변수 적용>

      //action point 1 : 매개변수 다시 쓰기(JS 엔진은 똑같이 이해한다)
      //action point 2 : 결과 예상하기
      //action point 3 : hoisting 적용해본 후 결과를 다시 예상해보기
      
      function a () {
      	var x = 1;
      	console.log(x);
      	var x;
      	console.log(x);
      	var x = 2;
      	console.log(x);
      }
      a(1);

      <호이스팅 적용>

      //action point 1 : 매개변수 다시 쓰기(JS 엔진은 똑같이 이해한다)
      //action point 2 : 결과 예상하기
      //action point 3 : hoisting 적용해본 후 결과를 다시 예상해보기
      
      function a () {
      	var x;
      	var x;
      	var x;
      
      	x = 1;
      	console.log(x);
      	console.log(x);
      	x = 2;
      	console.log(x);
      }
      a(1);

      자, 예상을

      1 → undefined → 2로 예상했지만

      실제로는

      1, 1, 2 라는 결과가 나옴

      호이스팅이라는 개념을 모르면 예측이 불가능한 어려운 결과

    2. 호이스팅 법칙 2 : 함수 선언은 전체를 호이스팅
      마찬가지로, 2가지 action points에 따라 진행

      <적용 >
      
      ```jsx
      //action point 1 : 결과 값 예상해보기
      //action point 2 : hoisting 적용해본 후 결과를 다시 예상해보기
      
      function a () {
      	console.log(b);
      	var b = 'bbb';
      	console.log(b);
      	function b() { }
      	console.log(b);
      }
      a();
      ```
      
      <호이스팅 적용>
      
      ```jsx
      //action point 1 : 결과 값 예상해보기
      //action point 2 : hoisting 적용해본 후 결과를 다시 예상해보기
      
      function a () {
      	var b; // 변수 선언부 호이스팅
      	function b() { } // 함수 선언은 전체를 호이스팅
      
      	console.log(b);
      	b = 'bbb'; // 변수의 할당부는 원래 자리에
      
      	console.log(b);
      	console.log(b);
      }
      a();
      ```
      
      해석을 편하게 하기 위해서 **함수선언문을 함수 표현식으로** 바꿔볼게요!
      
      ```jsx
      //action point 1 : 결과 값 예상해보기
      //action point 2 : hoisting 적용해본 후 결과를 다시 예상해보기
      
      function a () {
      	var b; // 변수 선언부 호이스팅
      	**var b = function b() { } // 함수 선언은 전체를 호이스팅**
      
      	console.log(b);
      	b = 'bbb'; // 변수의 할당부는 원래 자리에
      
      	console.log(b);
      	console.log(b);
      }
      a();
      ```
      
      이번에도 우리의 예상은 틀렸네요.
      
      > 에러(또는 undefined), ‘bbb’, b함수
      > 
      
      라고 나올 것 같았지만, 실제로는
      
      > b함수, ‘bbb’, ‘bbb’
      > 
      
      라는 결과가 나왔어요. 이 또한 호이스팅을 고려하지 않고는 결과를 예측하기가 매우 어려웠어요. 호이스팅을 다루는 김에, **함수의 정의방식 3가지**와 **주의해야 할 내용**을 살짝 짚고 넘어가 보도록 하겠습니다 😉
    3. 함수 선언문, 함수 표현식

      1. 함수 정의의 3가지 방식

        // 함수 선언문. 함수명 a가 곧 변수명
        // function 정의부만 존재, 할당 명령이 없는 경우
        function a () { /* ... */ }
        a(); // 실행 ok
        
        // 함수 표현식. 정의한 function을 별도 변수에 할당하는 경우
        // (1) 익명함수표현식 : 변수명 b가 곧 변수명(일반적 case에요)
        var b = function () { /* ... */ }
        b(); // 실행 ok
        
        // (2) 기명 함수 표현식 : 변수명은 c, 함수명은 d
        // d()는 c() 안에서 재귀적으로 호출될 때만 사용 가능하므로 사용성에 대한 의문
        var c = function d () { /* ... */ } 
        c(); // 실행 ok
        d(); // 에러!
      2. 주의해야 할 내용

        1. 함수 선언문, 함수 표현식

          다시 잠깐 교통정리

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

          • 그 객체 안에는 3가지가 존재한다.
            ✓ VariableEnvironment
            ✓ LexicalEnvironment
            ✓ ThisBindings
          • VE와 LE는 실행컨텍스트 생성 시점에 내용이 완전히 같고, 이후 스냅샷 유지 여부가 다르다.
          • LE는 다음 2가지 정보를 가지고 있다.
            ✓ record(=environmentRecord) ← 이 record의 수집과정이 hoisting
            ✓ outer(=outerEnvironmentReference)

          계실질적인 차이를 예시

          console.log(sum(1, 2));
          console.log(multiply(3, 4));
          
          function sum (a, b) { // 함수 선언문 sum
          	return a + b;
          }
          
          var multiply = function (a, b) { // 함수 표현식 multiply
          	return a + b;
          }

          LE는 record와 outer를 수집 그 중, record를 수집하는 과정에서 hoisting이 일어나고, 우리가 익히 알고있는대로 위로 끌어올려본 결과를 다시 써보면 아래와 같음

          // 함수 선언문은 전체를 hoisting
          function sum (a, b) { // 함수 선언문 sum
          	return a + b;
          }
          
          // 변수는 선언부만 hoisting
          
          var multiply; 
          
          console.log(sum(1, 2));
          console.log(multiply(3, 4));
          
          multiply = function (a, b) { // 변수의 할당부는 원래 자리
          	return a + b;
          };

          어함수 선언문과 함수 표현식은 hoisting 과정에서 극명한 차이를 보임.

        2. 함수 선언문을 주의해야 하는 이유

          ...
          
          console.log(sum(3, 4));
          
          // 함수 선언문으로 짠 코드
          // 100번째 줄 : 시니어 개발자 코드(활용하는 곳 -> 200군데)
          // hoisting에 의해 함수 전체가 위로 쭉!
          function sum (x, y) {
          	return x + y;
          }
          
          ...
          ...
          
          var a = sum(1, 2);
          
          ...
          
          // 함수 선언문으로 짠 코드
          // 5000번째 줄 : 신입이 개발자 코드(활용하는 곳 -> 10군데)
          // hoisting에 의해 함수 전체가 위로 쭉!
          function sum (x, y) {
          	return x + ' + ' + y + ' = ' + (x + y);
          }
          
          ...
          
          var c = sum(1, 2);
          
          console.log(c);
        3. 만약 함수 표현식이었다면…?

          ...
          
          console.log(sum(3, 4));
          
          // 함수 표현식으로 짠 코드
          // 함수 선언부만 위로 쭉!
          // 이 이후부터의 코드만 영향을 받아요!
          var sum = function (x, y) {
          	return x + y;
          }
          
          ...
          ...
          
          var a = sum(1, 2);
          
          ...
          
          // 함수 표현식으로 짠 코드
          // 함수 선언부만 위로 쭉!
          // 이 이후부터의 코드만 영향을 받아요!
          var sum = function (x, y) {
          	return x + ' + ' + y + ' = ' + (x + y);
          }
          
          ...
          
          var c = sum(1, 2);
          
          console.log(c);

          협업을 많이 하고, 복잡한 코드일 수록. 전역 공간에서 이루어지는 코드 협업일 수록 함수 표현식을 활용하는 습관을 들이도록

(4) LexicalEnvironment(2) - 스코프, 스코프 체인, outerEnvironmentReference(=outer)

  • 주요 용어
    1. 스코프

      1. 식별자에 대한 유효범위를 의미
      2. 대부분 언어에서 존재하구요, 당연하게도 JS에서도 존재
    2. 스코프 체인

      1. 식별자의 유효범위를 안에서부터 바깥으로 차례로 검색해나가는 것

        출처 : https://jess2.xyz/JavaScript/scope-chain-closure/

        출처 : https://jess2.xyz/JavaScript/scope-chain-closure/

    3. outerEnvironmentReference(이하 outer)

      그 두번째인 outer outer의 역할을 한 마디로 정의하자면

      스코프 체인이 가능토록 하는 것(외부 환경의 참조정보)라고 할 수 있음

      • 외부 환경의 참조정보 라는 말에 집중.
  • 스코프 체인
    1. outer는 현재 호출된 함수가 선언될 당시(이 말이 중요)의 LexicalEnvironment를 참조.
      참조한다는 말이 어려우면, 그 당시의 환경 정보를 저장한다. 정보로 이해해도 괜찮음

    2. 예를 들어, A함수 내부에 B함수 선언 → B함수 내부에 C함수 선언(Linked List)한 경우 어떻게 될까?

    3. 결국 타고, 타고 올라가다 보면 전역 컨텍스트의 LexicalEnvironment를 참조하게 됨

    4. 항상 outer는 오직 자신이 선언된 시점의 LexicalEnvironment를 참조하고 있으므로, 가장 가까운 요소부터 차례대로 접근 가능

    5. 결론 : 무조건 스코프 체인 상에서 가장 먼저 발견된 식별자에게만 접근이 가능

      
      var a = 1;
      var outer = function() {
      	var inner = function() {
      		console.log(a); // 이 값은 뭐가 나올지 예상해보자
      		var a = 3;
      	};
      	inner();
      	console.log(a); // 이 값은 또 뭐가 나올까요? 이유는?
      };
      outer();
      console.log(a); // 이 값은 뭐가 나올까?

      각각의 실행 컨텍스트는 LE 안에 record와 outer를 가지고 있고, outer 안에는 그 실행 컨텍스트가 선언될 당시의 LE정보가 다 들어있으니 scope chain에 의해 상위 컨텍스트의 record를 읽어올 수 있다.

profile
𝙸 𝚊𝚖 𝚊 𝚌𝚞𝚛𝚒𝚘𝚞𝚜 𝚍𝚎𝚟𝚎𝚕𝚘𝚙𝚎𝚛 𝚠𝚑𝚘 𝚎𝚗𝚓𝚘𝚢𝚜 𝚍𝚎𝚏𝚒𝚗𝚒𝚗𝚐 𝚊 𝚙𝚛𝚘𝚋𝚕𝚎𝚖. 🇰🇷👩🏻‍💻

0개의 댓글