본문 바로가기

개발 이야기

실행 컨텍스트 (Execution Context)

안녕하세요. 이제 막 기획자로 시작한 아기 기획자 소소한G 입니다.

비전공자 출신으로 IT 웹 기획자로서 살아남기위해 일주일에 두번 프로트엔드 개발 수업을 듣고 있어요.

온전히 비전공자의 시선으로 꼼꼼하게, 원초적으로 설명드리려고 합니다.

저처럼 초보자 웰컴이에용.

 

오늘은 실행 컨텍스트에 대해 알아봅시다.


실행 컨텍스트란

- 코드가 실행되는 맥락. 즉, 실행가능한 코드가 실행되기 위해 필요한 환경을 저장하는 것입니다.

- Scope, hoisting, this, function, closure 등 동작원리를 담고 있는 자바스크립트의 핵심원리입니다.

 

Javascript는 환경을 단위로 이해해야하기 때문에 실행 컨텍스트는 Javascript에서 꼭 알아야 합니다. 

실행컨텍스트는 함수안의 내용이며 실행되는 맥락입니다. 현재 실행되고 있는 주체라고 이해하면 됩니다. 

함수가 어디 실행되고 있는지 실행컨텍스트의 스택으로 관리가 가능합니다. 

*처음 코드를 실행하는 순간 모든 것을 포함하는 전역 컨텍스트가 생깁니다. 

 

컨텍스트의 원칙 4가지

1. 먼저 전역 컨텍스트 하나 생성 후, 함수 호출 시마다 컨텍스트가 생긴다.

2. 컨텍스트 생성 시 컨텍스트 안에 변수객체(arguments, vaiable), scope chain, this가 생성된다.

3. 컨텍스트 생성 후 함수가 실행되는데, 사용되는 변수들은 변수객체 안에서 값을 찾고, 없다면 스코프 체인을 따라 올라가며 찾는다.

4. 함수 실행이 마무리되면 해당 컨텍스트는 사라진다. 페이지가 종료되면 전역 컨텍스트가 사라진다. 

 

실행 컨텍스트를 이해하기 전 Javascript의 코드를 실행하기 위해 실행에 필요한 여러가지 정보를 알고 있어야 합니다. 

1. 전역변수 : 전역에서 사용하는 변수

2. 지역변수 : 함수 안에서 사용하는 변수

3. 객체의 프로퍼티 : 객체가 가지고 있는 고유한 것

4. 함수선언 : const data = function(){} 

5. 스코프(scope) : 변수의 유효범위(현재 실행되고 있는 곳)

* 변수(VO) 순서대로 가지고 와서 참조할 수 있는 것을 스코프체인(scope chain)이라 합니다.

 

0. global에서 시작

1. foo 실행.

(전역코드(global code)로 컨트롤이 진입하면 실행 컨텍스트가 생성되고 실행컨텍스트 스택에 쌓임)

2. foo를 실행하기 위해 foo 함수를 호출

3. bar을 실행

4. bar을 실행하기 위해 bar 함수를 호출

5. console.log를 실행

 

 

 

 

 

[실행컨텍스트 스택]

위 코드를 실행하면 실행 컨텍스트 스택이 생성하고 소멸합니다. 

* 스택(stack)은 순차적으로 쌓였다가 역순으로 사라집니다. (후입선출의 자료구조 = LIFO:Last In First Out)

* Javascript는 함수 기반 언어이며 스코프도 함수기반으로 만들어져 실행되는 함수가 쌓입니다. 

이 컨텍스트는 스택에 쌓이게 되고 컨트롤이 이동한다. 컨트롤이 실행 가능한 코드로 이동하면 논리적 스택 구조를 가지는 새로운 실행 컨텍스트 스택이 생성됩니다. 

 

 

위의 코드를 보고 스택이 어떻게 쌓이는지 알아봅시다. (코드는 위에서 아래로 읽습니다.)

즉, 시작과 마지막을 보면 실행 컨텍스트의 스택의 과정은 아래와 같습니다. 

1. 먼저 global 스택 생성 : 변수 x 와 함수 foo

2. foo의 호출(실행)이 있기 때문에 foo() 스택 생성 : foo() {} > {}안의 코드가 실행 컨텍스트가 됩니다. 

3. bar의 호출(실행)이 있기 때문에 bar() 스택 생성 

위와 같이, 실행 컨텍스트는 스택들이 순차적으로 쌓이고 역순으로 없어집니다.

- 가장 먼저들어간 것이 가장 마지막에 나오는 FILO(First In Last Out) 구조를 가집니다.

↔ 선입선출(FIFO : First In First Out) 이다. queue에서 이 구조를 가집니다. 

 

논리적 스택 구조를 가지는(컴퓨터 공학적 자료구조를 가지는) 실행컨텍스트의 스택 : 

global > foo > bar 함수 순서대로 쌓였다가 함수 실행 순서에 반대되어

bar > foo 이렇게 역순으로 파괴시키는 구조입니다. 

 

 

[실행 컨텍스트의 3가지 객체]

 

실행되고 있는 코드의 '환경'이라고 정의 내렸듯이 그 환경에는 3가지 객체가 존재합니다.

객체란? (우리가 표현할 수 있는 객체는 두가지가 있습니다.)

1. 문법에서의 객체 : {name : '박현지'}

2. class를 부여해서 동적할당으로 쓰이는 객체 > new 로 생성

 

1. Variable Object (VO) : 변수 객체

- 해당 스코프의 변수들

- 객체 형식으로 {} 으로 정보를 담고 있다. 

- 실행 컨텍스트의 property이므로 아래의 세가지 정보를 담는다. 

    1) 변수

    2) 매개변수 (parameter) / 인수정보 (arguments)

    3) 함수선언

 

- 변수객체는 전역 컨텍스트와 함수 컨텍스트에 따라 가리키는 객체가 다릅니다.

전역컨텍스트일 때 : 전역객체 (Global Object(GO)) : 전역변수와 전역함수를 프로퍼티로 가지고 있어요.

함수 컨텍스트일 때 : 활설객체 (Activation Object(AO)) : 함수안의 매개변수와 인수들의 정보를 배열의 형태로 담고 있는 arguments object가 추가됩니다.

  

새로운 객체를 VO가 직접 담고 있지 않고 따로 리스트를 만드는 이유는 무엇일까요? 

 

 스코프 체인을 이용할 때 이전 실행 컨텍스트의 VO를 쉽게 접근하기 위해서입니다. 

 즉, 공통적으로 사용하는 부분을 따로 리스트를 만들어서 이곳 저곳에 사용하기 위해서입니다. 

 

2. Scope Chain (SC) : 스코프 체인

- 자신과 상위 스코프들의 변수객체입니다.

- 상위 컨텍스트의 VO를 참조하여 해당하는 객체를 가져오는 것을 말합니다.

- 자신이 호출되기 전에 부모의 VO를 들고와서 사용하는 것입니다.

- 식별자 중 객체의 프로퍼티가 아닌 식별자, 즉 "변수를 검색하는 매커니즘"이다. 식별자 중에서도 변수가 아닌 상위의 VO를 가지고 옵니다. 

 

SC에넌 배열의 형태로 요소 하나에 VO하나를 넣습니다. 

[0, 1, 2, ...] 라고 할 때 

0 은 자기자신의 VO

1 은 자신의 부모 VO

2 는 자신의 부모의 부모 VO...

실행컨텍스트에 쌓인 순서대로 (GO)까지 들어간다.고 볼 수 있습니다. 

 

프로토타입 체인 (Prototype Chain)

- 식별자 중 변수가 아닌 객체의 프로퍼티를 검색하는 메커니즘, 즉 상위의 프로토타입을 가지고 오는 것

 

 

 

 

 

 

bar()의 코드를 읽습니다. console.log(x+y+z) 를 출력하기 위해 x, y, z변수의 정보가 있는지 확인합니다.

bar() {} 안에 변수 z가 선언되고 값도 있지만 x와 y는 정보가 없습니다. 

 

bar() 의 SC - 0은 자기자신의 VO

bar() 의 SC - 1은 foo()의 VO

bar() 의 SC - 2는 global의 VO가 연결되어 있습니다. 

 

 

 

 

x 와 y 는 정보가 없기 때문에 다른 실행 컨텍스트에 있는지 참조하기 위하여 상위 실행 컨텍스트(바로 밑에 있는 스택)의 VO를 참조합니다.

 

foo() {} 안에 변수 y가 선언되어 있습니다.

 

 x 의 정보를 찾기 위하여 foo() 의 상위 컨텍스트(바로 밑에 있는 스택)의 VO를 참조합니다.

 

변수 x, y, z 의 정보를 모두 가져왔기 때문에 console.log(x + y + z) 은 xxxyyyzzz 로 출력됩니다.

 

3. this Value : 실행컨텍스트 자기자신

 

 

스코프체인과 프로토타입 체인의 차이점

[스코프체인]

- 현재 실행되고 있는 영역을 기준으로 진행된다. 그래서 언제든지 스코프가 변경될 수 있습니다. 

- 즉, 체이닝 순서가 바뀔 수 있습니다.

* 체이닝 순서가 바뀐다? ex) 위의 코드를 참조해 보면, bar()을 어디서 실행하느냐에 따라서 bar을 실행할 수 있고 foo를 실행할 수 있습니다.

- 실행 컨텍스트 스택에 의해서 체이닝 순서가 고정되어 있지 않습니다.

 

[프로토타입 체인]

- 현재 실행되고 있는 영역 기준이 아닌 상속받은 그 객체의 기준으로 객체를 생성하면 끝, 더 이상 체이닝이 실행되지 않습니다. 

- 원형이 바뀌지 않기 때문에 체이닝 순서가 바뀔 수 없어요.

- 고정되어 있는 것을 상속받습니다.

 

 

 

렉시컬 스코프 (Lexical : 유효범위)

- 현재 내가 변수를 실행할 수 있는 환경, 함수를 실행할 수 있는 환경

- foo()는 렉시컬 스코프로 변수 x를 사용할 수 있습니다.

 

Activation object : 현재 실행되고 있는 스택. 즉, 스택의 최상위에 있는 것

 

 

 

실행 컨텍스트의 생성과정

 

- 실행컨텍스트 스택이 아무 것도 없을 때

window

 

global 코드에 진입하기 전에 전역객체(global object)가 생성됩니다.

 

- global 코드에 진입했을 때

 

1) global 스택 생성 

(스택 안에 VO, SC, this의 정보가 들어간다)

실행 컨텍스트 스택에 global스택이 쌓이게 됩니다.


global EC

초기 상태에는 DOM, BOM, Built-in Object등이 설정됩니다.

 

2) SC 생성과 초기화

실행 컨텍스트가 생성되면 가장 먼저 실행된다. 

 

이 스코프 체인은 전역 실행 컨텍스트를 바탕으로 하기 때문에 전역 객체를 포함하는 리스트가 됩니다.

VO - GO 연결, 스코프체인에서 배열을 가지고 있는데 0 즉 첫번째를 GO로 만들고 VO의 값이 GO를 가리키게 만듭니다.

 

global - SC

0 - GO

SC - [0] - GO 의 형태로 VO 리스트가 들어갑니다.

SC 에 배열이 들어가지만 전역 실행 컨텍스트가 바탕이므로 배열 요소는 0 하나만 존재합니다.

 

3) 변수 객체화 (Variable Instantiation)

VO에 프로퍼티와 값을 추가합니다. 

 

VO에는 변수, 매개변수와 인수정보(arguments), 함수선언을 추가합니다. 

 

[변수의 객체화 순서]
1. (함수 코드일 때)
매개변수가 VO 의 프로퍼티, 인수가 값으로 설정


2.  (함수 호이스팅)
해당 실행 컨텍스트 코드 중 함수 선언이 있다면 함수명을 VO 의 프로퍼티, 함수의 객체가 값으로 설정


3. (변수 호이스팅)
해당 실행 컨텍스트 코드 중 변수 선언이 있다면 변수명을 VO 의 프로퍼티, undefined 가 값으로 설정

 

앞의 코드로 예시를 볼까요? 

전역 실행 컨텍스트에는 변수 x와 함수 foo가 있는 것을 볼 수 있다. 변수 객체화 순서의 1번은 해당사항이 아니기 때문에 제외합니다. 

변수 객체화 순서에 따라 함수 foo 설정 변수 x를 설정하도록 합니다.

 

- 변수 값이 넣어지면 foo함수를 실행합니다. (변수 객체화 순서 2번에 해당)

 

VO - GO

foo function object [[scopes]]


여기서 잠깐,

scopes란?

- VO에 있는 함수는 실행이 될 때 어느 scope에서 왔는지 알려주기 위해 [[scopes]]를 사용합니다.

* 넌 여기서 왔으니 이 배열을 가지고 있어야 해 하고 넘겨주는 역할을 합니다. 

실행될 때 함수의 스택을 생성한 후 [[scopes]]를 보고 해당함수의 스코프에 하위 스택 VO를 참조할 수 있게됩니다. 

 

4) this value 결정

this value 가 결정되기 이전에 this 는 전역 객체를 가리키고 있다가 함수 호출 패턴에 의해 this 에 할당되는 값이 결정됩니다.

 

 

지금까지 실행 컨텍스트, 스코프에 대해 알아보았습니다.

 

반응형