02. 실행 컨텍스트

Joy·2023년 11월 22일
0

📍 실행 컨텍스트

  • 실행 할 코드에 제공할 환경 정보들을 모아 놓은 객체
  • javascript의 동적 언어로서의 성격을 가장 잘 파악할 수 있는 개념
컨텍스트란?
번역하면 상황, 문맥, 맥락. 실행할 코드(함수)의 주변 상황이라고 이해할 수 있다.
그러한 주변 환경 정보들의 묶음이 실행 컨텍스트인 것이다.

스택과 큐

  • 스택 : 출입구가 하나뿐인 깊은 우물같은 데이터구조 (a,b,c,d 저장시 d,c,b,a 순서로 꺼냄)
  • 큐 : 양쪽이 모두 열려있는 파이프구조[보통 한쪽은 입력, 한쪽은 출력 담당] (a,b,c,d 저장시 a,b,c,d 순서로 꺼냄)
1. 동일한 환경에 있는 코드를 실행할 때 필요한 환경 정보들을 모아 컨텍스트를 구성
2. 콜스택에 이를 쌓아올림
3. 가장 위에 있는 컨텍스트와 관련 있는 코드를 실행하는 식으로 전체 코드의 환경과 순서를 보장
“동일한 환경”이란? 하나의 실행 컨텍스트를 구성할 수 있는 방법으로 전역공간, eval() 함수, 함수 등

💡 실행 컨텍스트가 활성화 되는 시점에 javascript에서 발생하는 현상

  • 호이스팅이 발생한다(선언된 변수를 위로 끌어올린다)
  • 외부 환경 정보를 구성한다
  • this 값을 설정한다.
    이로 인해 다른 언어에서 발견할 수 없는 특이한 현상들이 발생한다.

대표적으로 호이스팅이 일어나게 하는 환경 레코드(enviroment record),
클로저가 일어나게 하는 외부 렉시컬 참조(outer LexicalEnvironment Reference),
this 정보(this Binding)등의 정보를 포함하고 있습니다.
'전역영역', 'eval함수로 실행된 경우', '함수를 생성한 경우' 이렇게 3가지의 경우에만 실행 컨텍스트가 생성된다.

VariableEnvironment

현재 컨텍스트 내의 식별자들에 대한 정보 + 외부 환경 정보
실행 컨텍스트 생성 시 VariableEnvironment에 정보를 먼저 담은 다음, 이를 그대로 복사해서 LexicalEnvironment를 만들고, 이후에는 LexicalEnvironment를 주로 활용
변경 사항이 반영 되지 않음

LexicalEnvironment

현재 컨텍스트 내의 식별자들에 대한 정보 + 외부 환경 정보 (VariableEnvironment와 동일)
현재 컨텍스트 내부에는 a, b, c와 같은 식별자들이 있고 그 외부 정보는 D를 참조하도록 구성돼 있다.”와 같은 정보
변경 사항이 실시간으로 반영됨

ThisBinding

식별자가 바라봐야 할 대상 객체

📍 environmentRecord

  • 현재 컨텍스트와 관련된 코드의 식별자(변수명) 정보들이 저장되는 곳
  • 컨텍스트를 구성하는 함수에 지정된 매개변수명, 선언된 함수(그 자체), var로 선언된 변수명 등이 저장 (let, const는 아님)
  • 컨텍스트 내부 전체를 처음부터 끝까지 훑어나가며 순서대로 수집

변수 수집 과정을 모두 마쳤더라도 실행 컨텍스트가 관여할 코드들은 아직 실행 전
=> 코드 실행 전임에 불구하고 자바스크립트 엔진은 이미 해당 환경에 속한 코드의 변수명을 모두 알게 있게 됨 (호이스팅)

📍 호이스팅

함수 내 선언들을 해당 함수 스코프의 최상단으로 끌어올려서 선언되는 것을 의미

호이스팅 규칙

  • 호이스팅 시 변수명만 끌어올리고 할당 과정은 원래 자리에 남겨둠 (environmentRecord는 현재 실행될 컨텍스트의 대상 코드 내에 어떤 식별자들이 있는지에만 관심이 있고, 각 식별자에 어떤 값이 할당될 것인지는 관심이 없기 때문)
  • 변수는 선언부와 할당부를 나누어 선언부만 끌어올리는 반면 함수 선언은 함수 전체를 끌어올림
  • 호이스팅이 끝난 상태에서의 함수 선언문은 함수명으로 선언한 변수에 함수를 할당한 것처럼 여길 수 있음

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

  • 변수의 선언문만 호이스팅 되고 할당부는 그대로

2) 함수 선언문 호이스팅

  • 함수 선언 전체를 호이스팅 함

3) 함수 표현식 호이스팅

  • 변수 선언부만 호이스팅 되고 할당부는 그대로

💡 함수 표현식을 쓰는 이유
함수 선언문을 사용할 경우, 함수 선언보다 호출이 우선되어도 문제없이 코드가 돌아간다. 이러한 환경은 개발자가 의도하지 않는 동작이 일어날 수 있다는 것을 의미하고, 오류 발생 시 원인을 파악하기가 쉽지 않다.
따라서 상대적으로 안전한 함수 표현식을 쓰는 것이 좋다.

📍 outerEnvironmentReference와 Scope

Scope

  • 스코프란 식별자에 대한 유효범위

Scope A의 외부에서 선언한 변수는, A의 외부/내부 모두 접근 가능하다.
A의 내부에서 선언한 변수는 오직 A의 내부에서만 접근할 수 있다.

scope chain

  • 식별자의 유효범위를 안에서 바깥으로 차례로 검색해나는 것
  • 이를 가능하게 하는 것이 outerEnvironmentReference이다.

outerEnvironmentReference는 호출된 함수가 선언될 당시의 LexicalEnvironment를 참조한다.
연결 리스트의 형태를 띠며, 선언 시점의 LexicalEnvironment를 계속 찾아 올라가다 보면 마지막에는 전역 컨텍스트의 LexicalEnvironment가 있을 것이다.
구조적인 특성상 여러 스코프에서 동일한 식별자를 선언한 경우, 무조건 스코프 체인 상에서 가장 먼저 발견된 식별자에만 접근 가능하다.

스코프 체인 예시

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

outer();
console.log(a);

호이스팅 결과

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

outer();
console.log(a);

스코프 체인에 따른 출력 결과

undefined // inner안에서 a를 발견했지만 값이 없음
1 // outer에서 a발견하지 못함 -> 바깥에서 탐색 -> 전역에서 a발견
1 // 전역에서 a발견

📍 this

다른 언어들에서는 this는 해당된 객체를 의미하지만, 자바스크립트 상에서 this는 어디서든 사용될 수 있다.
(=상황에 따라 this가 바라보는 대상이 달라진다.)

this는 자바스크립트의 함수 안에서 약속되어 사용되는 일종의 변수.
this는 함수 내에서 함수 호출 맥락(= context, 상황에 따라 가변적인 경우)를 의미한다.

함수 호출

function func(){
    if(window === this){
        console.log("window === this");
    }
}
func(); 
//결과값 : window === this

함수안에서 this는 전역객체(window)를 의미한다.

메소드의 호출

var o = {
    func : function(){
        if(o === this){
            console.log("o === this");
        }
    }
}

o.func();  
//결과값 : o === this

메소드를 호출하면 메소드에 소속되어있는 this는 해당되어있는 객체를 의미한다.

함수의 호출과 메소드의 호출은 큰 차이가 없는게, 함수의 호출은 결국 window.func(); 이기 때문에 전역객체의 메소드를 호출하는것과 같다.

결론 : 함수안에서의 this는 그 함수가 소속되어있는 객체를 가르킨다.

명시적으로 this를 바인딩하는 방법

상황에 따라 달라지는 this를 원하는 값으로 바인딩하는 방법

call(), apply()

call 메서드는 호출 주체인 함수를 즉시 실행하도록 하는 명령으로, 첫 번째 인자를 this로 바인딩하고 이후의 인자들을 호출할 함수의 매개변수로 전달하면 된다.
apply 메서드는 call과 동일한 역할을 수행하며, 두 번째 인자를 배열로 받는다는 점만 다르다.

var func = function (a, b, c) {
  console.log(this, a, b, c);
}

func(1, 2, 3); // window 1 2 3
func.call({x: 1}, 4, 5, 6); // {x: 1} 4 5 6
func.apply({x: 2}, [4, 5, 6]); // {x: 2} 4 5 6

call, apply 메서드 활용
유사배열객체에 call, apply 메서드를 이용하면 배열 메서드를 활용할 수 있게 된다.
ES6에서는 이를 위해 Array.from(순회 가능한 모든 종류의 데이터 타입을 배열로 전환) 메서드를 도입했다.

bind()

bind 메서드는 call과 비슷하지만, 즉시 호출하지 않고 넘겨받은 this 및 인수들을 바탕으로 새로운 함수를 반환하기만 하는 메서드이다.
함수에 this를 미리 적용할 때 혹은 부분 적용 함수를 구현할 때 활용할 수 있다.
bind를 통해 새로 만든 함수는 name 프로퍼티에 bound라는 접두어가 붙는다는 특징이 있다. 따라서 call이나 apply보다 코드를 추적하기 수월하다.

var func = function(a, b, c, d){
    console.log(this, a, b, c, d);
};

func(1, 2, 3, 4); //window 1 2 3 4

//this 적용
var bindFunc1 = func.bind({ x:1 });
bindFunc1(5, 6, 7, 8); //{ x:1 } 5 6 7 8

//부분 함수 적용
var bindFunc2 = func.bind({ x:1 }, 4, 5);
bindFunc2(6, 7); //{ x:1 } 4 5 6 7
bindFunc2(8, 9); //{ x:1 } 4 5 8 9

⭐️ 정리 ⭐️

  • 실행 컨텍스트
    제공할 환경 정보들을 모아 놓은 객체
    실행 컨텍스트 객체는 활성화 되는 시점에 VariableEnvironment, LexicalEnvironment, ThisBinding의 세 가지 정보 수집
    VariableEnvironment와 LexicalEnvironment는 environmentRecord(매개 변수명, 식별자, 함수명등 수집)와 바로 직전 컨텍스트의 LexicalEnvironment 정보를 참조하는 outerEnvironmentReference로 구성
    실행 컨텍스트 생성할 때, VariableEnvironment는 초기상태 유지, LexicalEnvironment는 실행 도중 변경사항 즉시 반영

  • 호이스팅
    실행 컨텍스트가 관여하는 코드의 최상단에 끌어올림
    변수 언건과 할당이 동시에 이루어지면 '선언부'만 호이스팅한다 => 선언문과 표현식의 차이 발생

  • 스코프
    변수의 유효범위
    outerEnvironmentReference는 해당 함수 선언된 위치인 LexicalEnvironment를 참조
    전역 컨텍스트의 LexicalEnvironment까지 탐색해도 변수 찾지못하면 undefined 반환
    전역변수: 전역 컨텍스트의 LexicalEnvironment 담긴 변수

  • this
    실행 컨텍스트 활성화하는 당시 지정된 this가 저장
    지정되지 않으면 전역 객체 저장

profile
🐣

0개의 댓글