실행컨텍스트의 동작원리

gwjeon·2020년 1월 15일
1


React 공부중 클래스 컴포넌트, 화살표 함수에서 this가 헷갈리기 시작했다. 이리 저리 검색하며 공부하다보니 실행컨텍스트에 대해 개념이 부족해 정리하려한다.

실행컨텍스트란 자바스크립트에서 실행 가능한 코드들이 실행되는 환경을 말한다.
실행컨텍스트만 재대로 잘 이해 하고있으면 자바스크립트의 동작환경을 이해할 수 있다.


아래의 코드 예제를 보자.

var a = '안녕하세요';
function func1() {
    var b = '제 이름은';
    function func2() {
        var c = '홍길동 입니다';
        console.log(a,b,c);
    }
    func2();
}
func1();

다음과 같은 실행가능한 코드 영역이 있다. 이 코드 영역이 실행 될 때 실행 컨텍스트의 동작을 전체적으로 살펴보자.

context image

자바스크립트는 제일 처음으로 실행 컨텍스트 스택을 생성한다. 우선 처음으로 전역 실행 컨텍스트가 생성되고 가장 먼저 스택에 쌓이고 제어권을 가진다. 그 후 코드 실행 순서에 따라 func1 함수 실행 컨텍스트가 생성되고 쌓이고 직전의 컨텍스트로 부터 제어권을 가지고 오며 func2 함수 실행 컨텍스트 역시 생성되고 쌓이게 되며 직전의 컨텍스트로 부터 제어권을 가지고 온다.

그 후 함수 실행이 끝나면 해당 함수의 실행 컨텍스트는 삭제되며 직전의 실행 컨텍스트로 제어권을 반환한다.


실행 컨텍스트의 구성요소

실행 컨텍스트는 각각의 물리적인 객체를 가지며 3가지를 프로퍼티로 생성한다.

실행 컨텍스트 = {
  변수객체,
  Scope,
  this
}

실행 컨텍스트는 다음과 같은 순서로 진행 된다.


1. Scope Chain의 생성과 초기화

실행 컨텍스트는 각자 자기 자신만의 Scope 프로퍼티를 가지고 있는데 이 것이 Scope Chain 리스트를 가르킨다. Scope Chain 역시 객체이며 이 객체의 프로퍼티는 각 컨텍스트의 변수객체를 가르킨다. 예를 들어 전역컨텍스트의 Scope 프로퍼티가 가르키는 Scope Chain은 0번이라는 프로퍼티로 자신의 변수객체를 참조하고 이 전역 컨텍스트에서 호출된 함수 컨텍스트의 Scope 프로퍼티가 가르키는 Scope Chain은 전역컨텍스트의 Scope 프로퍼티가 가르키는 Scope Chain을 포함하여 1번이라는 프로퍼티로 자신의 변수객체를 가르킨다.

전역 컨텍스트의 Scope

Scope 프로퍼티 = {
	scope chain: {
    0: 자신의 변수객체
    }
}

전역 컨텍스트에서 호출된 함수 컨텍스트의 Scope

Scope 프로퍼티 = {
	scope chain: {
	0: 전역 컨텍스트의 변수객체
    1: 자신의 변수객체
    }
}

이러한 방식을 통하여 코드 실행시 변수를 스코프 체인에 따라 탐색하며 찾아 낸 변수를 실행한다. 예를 들어

var a = '1';
function func1() {
    console.log(a);
}
func1();

와 같은 코드에서는 전역 컨텍스트에서 func1 함수 호출후 func1 함수 컨텍스트가 생성된다. console.log(a)를 통하여 a를 출력하려고 하지만 현재 자신의 변수 객체에는 a라는 변수가 없다. 이후 Scope Chain을 따라 전역 컨텍스트의 변수객체에서 변수 a를 찾아 출력한다.

또 한 이러한 Scope Chain을 이용하여 A함수 컨텍스트 내부에 B함수 컨텍스트가 존재 할때 A함수 컨텍스트가 소멸되어도 B함수 컨텍스트에서 A함수 컨텍스트의 변수객체에 접근이 가능하다. 이것이 바로 클로저이다.


2. 변수객체 초기화

변수 객체란 아래의 정보를 담는 객체이다. 다음과 같은 프로퍼티를 가진다.

전역객체

전역객체 = {
	변수,
    함수 선언
}

활성객체

활성객체 = {
	arguments,
	변수,
    함수 선언
}

변수객체란 전역 실행 컨텍스트의 경우 전역객체를 가르키며 함수 실행 컨텍스트의 경우 활성객체를 가르킨다. 두 객체의 다른점은 함수가 가지는 arguments 프로퍼티가 있고 없고의 차이이다. 전역객체에는 arguments 프로퍼티가 없다. 혼동을 줄이기 위해서 두가지 모두 변수객체라고 얘기하겠다.

변수객체는 다음과 같은 순서로 초기화 된다.


1. arguments 프로퍼티 등록

가장 먼저 전역 컨텍스트가 생성되고 Scope 프로퍼티를 초기화한다. 그 후 arguments 프로퍼티에 값을 저장해야 하는데 전역 컨텍스트의 경우 건너뛴다. 함수 컨텍스트의 경우 함수의 arguments를 저장한다.


2. 함수 선언문 등록

함수 선언문 방식으로 선언된 함수들을 함수 선언 프로퍼티에 저장한다.


3. 변수 등록

해당 컨텍스트에 존재하는 모든 변수들을 저장한다. 이때 함수 표현식으로 선언된 함수 역시 변수에 해당한다.


- 변수와 함수의 호이스팅

이 과정에서 호이스팅이 일어나는 이유를 알 수 있는데 이 것은 함수와 변수의 선언 방식에 따라 초기화를 하게 되는 순서가 달라진다. 순서는 다음과 같다.

1. 선언 단계

변수객체에 함수또는 변수를 등록한다. 함수 선언문의 경우 선언과 동시에 프로퍼티로 함수 이름이 할당되고 값으로 함수객체 자체가 할당된다.

2. 초기화 단계

변수객체에 등록된 변수에 메모리를 할당하며 undefined로 초기화한다.

3. 할당 단계

실제 코드가 실행되면서 선언문을 만나면 값을 할당한다.

위와 같은 단계로 이루어 지는데 함수 선언문의 경우 선언과 동시에 변수객체에 할당이 이루어짐으로 코드내에서 함수선언문을 만나기 전에도 함수를 호출 할 수 있다. 변수의 경우 키워드에 따라 달라지는데 var 키워드의 경우 선언단계와 초기화단계가 동시에 이루어진다. 그로 인해 변수 선언을 만나기 전에도 변수에 접근 할 수 있다 단 값은 undefined이며 변수 선언문을 만난 후에 값이 할당된다. let과 const 키워드의 경우 선언단계와 초기화단계가 분리되어 이루어짐으로 변수선언문을 만나기 전까지는 초기화가 되지 않기 때문에 접근하게 되면 에러를 발생한다. 명확히 말하면 접근 할 수는 있지만(let과 const도 호이스팅이 된다.)아직 초기화를 거치지 않았기 때문에 에러를 발생한다.


3. this 결정

this는 함수의 호출 패턴에 의해 결정 되기 때문에 this는 일반적으로 전역객체를 가르키고 호출 패턴에 따라 할당된다.


동작 순서 확인하기

지금까지 실행 컨텍스트의 동작에 필요한 내용을 정리 하였다. 글 시작에 정리한 코드를 바탕으로 실제 실행되는 실행 컨텍스트를 정리해보자.

var a = '안녕하세요';
function func1() {
    var b = '제 이름은';
    function func2() {
        var c = '홍길동 입니다';
        console.log(a,b,c);
    }
    func2();
}
func1();

위에 있던 코드를 가져왔다. 자바스크립트에서 실행 컨텍스트에 진입하면 가장 먼저 실행 컨텍스트 스택이 생겨난다. 그 후 스택에 전역 실행 컨텍스트가 생성된다.

그 후 전역 실행 컨텍스트의 구성요소가 걸정된다. 가장 먼저 Scope를 초기화한다.
```javascript
전역 실행 컨텍스트 = {
	Scope: {
	0: 자기 자신의 변수객체
	}
}

그 후 변수객체를 초기화한다.

전역 실행 컨텍스트 = {
	Scope: {
	0: 자기 자신의 변수객체
	}
    변수객체: {
    a : undefined,
    func1 : 함수객체
    }
}

this를 결정한다.

전역 실행 컨텍스트 = {
	Scope: {
	0: 자기 자신의 변수객체
	}
    변수객체: {
    a : undefined,
    func1 : 함수객체
    }
    this: 전역
}

이 후 코드가 실행되며 변수 a에 값을 '안녕하세요'로 할당하고 func1 함수를 호출하며 func1 함수 컨텍스트가 생성된다. 이 때 실행 컨텍스트 스택에 쌓이게 되며 제어권이 전역 컨텍스트에서 함수 컨텍스트로 이동한다.

함수 실행 컨텍스트가 생성 되었으니 순서에 따라 다시 한번 더 구성요소를 결정해보자.
가장 먼저 Scope가 초기화된다.

func1 함수 실행 컨텍스트 = {
	Scope: {
	0: 전역 컨텍스트 변수객체
    1: 자기 자신의 변수객체
	}
}

그 후 변수객체를 초기화한다.

func1 함수 실행 컨텍스트 = {
	Scope: {
	0: 전역 컨텍스트 변수객체
    1: 자기 자신의 변수객체
	}
    변수객체: {
    arguments: null,
    b: undefined;
    func2 : 함수객체
    }
}

this를 결정한다.

func1 함수 실행 컨텍스트 = {
	Scope: {
	0: 전역 컨텍스트 변수객체
    1: 자기 자신의 변수객체
	}
    변수객체: {
    arguments: null,
    b: undefined;
    func2 : 함수객체
    }
    this: 전역
}

이 후 코드가 실행되며 변수 b에 값을 '제 이름은'로 할당하고 func2 함수를 호출하며 func2 함수 컨텍스트가 생성된다. 이 때 실행 컨텍스트 스택에 쌓이게 되며 제어권이 func1 컨텍스트에서 func2 컨텍스트로 이동한다. 이제 또 func2 함수 컨텍스트를 생성해보자.

Scope가 초기화 된다.

func2 함수 실행 컨텍스트 = {
	Scope: {
	0: 전역 컨텍스트 변수객체
    1: func1 함수 컨텍스트 변수객체
    2: 자기 자신의 변수객체
	}
}

그 후 변수객체를 초기화한다.

func2 함수 실행 컨텍스트 = {
	Scope: {
	0: 전역 컨텍스트 변수객체
    1: func1 함수 컨텍스트 변수객체
    2: 자기 자신의 변수객체
	}
    변수객체: {
    arguments: null,
    c: undefined;
    }
}

this를 결정한다.

func2 함수 실행 컨텍스트 = {
	Scope: {
	0: 전역 컨텍스트 변수객체
    1: func1 함수 컨텍스트 변수객체
    2: 자기 자신의 변수객체
	}
    변수객체: {
    arguments: null,
    c: undefined;
    }
    this: 전역
}

이 후 코드가 실행되며 변수 b에 값을 '홍길동 입니다'로 할당하고 console.log(a,b,c)를 통해 '제 이름은 홍길동 입니다'가 출력된다. 이 후 func2 컨텍스트가 소멸되면서 제어권이 func1로 이동하고 func1 컨텍스트도 소멸되고 제어권이 전역 컨텍스트로 이동하고 최종적으로 브라우저가 종료되면 전역 컨텍스트가 소멸된다.

참고:https://poiemaweb.com/ , 인사이드 자바스크립트

profile
ansuzh

0개의 댓글