Execution Context

sangminnn·2020년 4월 1일
0

글로 정리하는 JS

목록 보기
2/5

개념 ..?

코드가 실행될 때, Javascript는 실행해야하는 여러가지 코드들의 초안을 그린다.

이에 대한 순서로는

  1. 컨트롤이 실행 가능한 코드로 이동하면 논리적 스택 구조를 가지는 새로운 실행 컨텍스트 스택이 생성된다. 스택은 LIFO(Last In First Out, 후입 선출)의 구조를 가지는 나열 구조이다.

  2. 전역 코드(Global code)로 컨트롤이 진입하면 전역 실행 컨텍스트가 생성되고 실행 컨텍스트 스택에 쌓인다. 전역 실행 컨텍스트는 애플리케이션이 종료될 때(웹 페이지에서 나가거나 브라우저를 닫을 때)까지 유지된다.

  3. 함수를 호출 하면 해당 함수의 실행 컨텍스트가 생성되며 직전에 실행된 코드 블록의 실행 컨텍스트 위에 쌓인다.

  4. 함수 실행이 끝나면 해당 함수의 실행 컨텍스트를 파기하고 직전의 실행 컨텍스트에 컨트롤을 반환한다.

    여기서 2번 내용.

전역 코드(Global code)로 컨트롤이 진입하면 전역 실행 컨텍스트가 생성되고 실행 컨텍스트 스택에 쌓인다. 전역 실행 컨텍스트는 애플리케이션이 종료될 때(웹 페이지에서 나가거나 브라우저를 닫을 때)까지 유지된다.

이 내용이 바로 전역 변수를 사용에 대해 기피해야 하는 이유이다.

해당 웹 페이지에서 나가거나 브라우저를 닫을 때 까지는 어느 곳에서든 전역 변수에 접근이 가능하기 때문 !

실행 컨텍스트는 실행 가능한 코드를 형상화하고 구분하는 추상적인 개념이지만 물리적으로는 객체의 형태를 가지며 아래의 3가지 프로퍼티를 소유한다.

  1. Variable Object(변수 객체) - 해당 컨텍스트의 구성 요소에 대해 정리하는 객체
  2. Scope Chain(스코프 체인) - 해당 컨텍스트와 연관이 있는 일종의 리스트
  3. This Value(This 값) - 해당 컨텍스트에서 This 가 어떤 값을 가리키는 지에 대한 값.

Variable Object

해당 실행 컨텍스트가 생성되면 자바스크립트 엔진은 실행에 필요한 여러 정보들을 담을 객체를 생성하는데 이를 변수 객체라고 한다.

변수 객체에 들어가는 값으로는

  • 변수
  • 매개변수(parameter)와 인수 정보(arguments)
  • 함수 선언(함수 표현식은 제외)

가 있고,

전역 컨텍스트의 경우는 모든 전역 변수, 전역 함수가 전역 객체에 들어가고,

함수 컨텍스트의 경우는 내부 함수, 지역 변수, 매개 변수에 대한 내용들이 함수 객체에 들어간다.

Scope Chain ( 부제: Closure )

스코프 체인(Scope Chain)은 일종의 리스트로서 전역 객체와 중첩된 함수의 스코프의 레퍼런스를 차례로 저장하고 있다.

여기서 중요하다고 생각하는 부분으로는

 전역 객체와 중첩된 함수의 스코프의 레퍼런스를 차례로 저장

이 부분인데, 여기서 레퍼런스 라는 단어가 가장 중요하다고 생각한다.

그 이유로는 이전 글에서 언급했던 실행 컨텍스트의 특성을 활용한 정보 은닉 방식인 클로저에 관한 내용 때문이다.

클로저는 보통 위에서 언급한 실행 컨텍스트의 소멸 특성, 그리고 방금 언급한 레퍼런스 방식의 결합체라고 볼 수 있는데,

클로저는 클로저로 사용할 함수의 실행 컨텍스트 내부에 원하는 값을 넣어두고, 해당 함수의 실행 컨텍스트가 종료되어 파기되어야하는 상황에서 그 값을 참조하는 외부 값이 존재할 경우에, 그 값은 파기되지 않고 그대로 외부 값의 참조 값으로 존재하게 된다.

이렇게 될 경우 외부에서는 내부의 값에 대해 정확하게 알 수 없지만, 그 환경 값을 기억하고 있는 참조 값을 외부에서 어떠한 값을 저장 할 수 있는 공간으로 활용할 수 있게 된다.

이를 클로저(Closure) 라고 하는 것이다.

생성된 함수 객체는 [[Scopes]] 프로퍼티를 가지게 된다. [[Scopes]] 프로퍼티는 함수 객체만이 소유하는 내부 프로퍼티(Internal Property)로서 함수 객체가 실행되는 환경을 가리킨다. 따라서 현재 실행 컨텍스트의 스코프 체인이 참조하고 있는 객체를 값으로 설정한다. 내부 함수의 [[Scopes]] 프로퍼티는 자신의 실행 환경(Lexical Enviroment)과 자신을 포함하는 외부 함수의 실행 환경과 전역 객체를 가리키는데 이때 자신을 포함하는 외부 함수의 실행 컨텍스트가 소멸하여도 [[Scopes]] 프로퍼티가 가리키는 외부 함수의 실행 환경(Activation object)은 소멸하지 않고 참조할 수 있다. 이것이 클로저이다. (From. PoiemaWeb)

실행 컨텍스트 생성 순서

  1. 스코프 체인의 생성과 초기화
  2. Variable Instantiation(변수 객체화) 실행
  3. this value 결정

전역 실행 컨텍스트에 접근했다면 먼저 전역 scope부터 스코프 체인의 값으로 들어가고,

함수 실행 컨텍스트에 접근했다면 해당 함수 scope부터 시작해서 해당 함수의 외부 함수가 있을 경우에는 외부 함수 scope가 값으로 들어가며 마지막 값으로는 전역 scope가 들어온다.

변수 객체화 과정

  1. (Function Code인 경우) 매개변수(parameter)가 Variable Object의 프로퍼티로, 인수(argument)가 값으로 설정된다.
  2. 대상 코드 내의 함수 선언(함수 표현식 제외)을 대상으로 함수명이 Variable Object의 프로퍼티로, 생성된 함수 객체가 값으로 설정된다.(함수 호이스팅)
  3. 대상 코드 내의 변수 선언을 대상으로 변수명이 Variable Object의 프로퍼티로, undefined가 값으로 설정된다.(변수 호이스팅)

+) var 키워드로 선언된 변수는 선언 단계와 초기화 단계가 한번에 이루어진다.

따라서 변수 선언문 이전에 변수에 접근하여도 Variable Object에 변수가 존재하기 때문에 에러가 발생하지 않는다. 다만 undefined를 반환한다. 이러한 현상을 변수 호이스팅(Variable Hoisting)이라한다.

아직 변수 x는 ‘xxx’로 초기화되지 않았다. 이후 변수 할당문에 도달하면 비로소 값의 할당이 이루어진다.

지금까지 살펴본 실행 컨텍스트는 아직 코드가 실행되기 이전이다. 하지만 스코프 체인이 가리키는 변수 객체(VO)에 이미 함수가 등록되어 있으므로 이후 코드를 실행할 때 함수선언식 이전에 함수를 호출할 수 있게 되었다.

이때 알 수 있는 것은 함수선언식의 경우, 변수 객체(VO)에 함수표현식과 동일하게 함수명을 프로퍼티로 함수 객체를 할당한다는 것이다. 단, 함수선언식은 변수 객체(VO)에 함수명을 프로퍼티로 추가하고 즉시 함수 객체를 즉시 할당하지만 함수 표현식은 일반 변수의 방식을 따른다. 따라서 함수선언식의 경우, 선언문 이전에 함수를 호출할 수 있다. 이러한 현상을 함수 호이스팅(Function Hoisting)이라 한다.

(From. PoiemaWeb)

그렇다면 여기서 함수 표현식과 선언식의 차이는 ?

function hello(name) {
	return `hello, ${name}`
};

위와 같은 방식은 직관적으로도 function으로 시작하기 때문에, 함수 선언 식 이라고 부르고

let hello = function(name) {
	return `hello, ${name}`
};

위와 같은 방식은 해당 함수에 대해 변수로 표현 해주었기 때문에, 함수 표현 식 이라고 부른다.

정리

함수 선언식의 경우는 함수 호이스팅으로 인해 할당 전에도 호출이 가능하지만,

함수 표현식의 경우는 직관적으로 변수 형태이기 때문에 값을 할당하기 전에는 변수 호이스팅으로 인해 undefined값이 나타나게 된다.

( 더 자세한 내용은 https://joshua1988.github.io/web-development/javascript/function-expressions-vs-declarations/ )

this value 할당

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

그렇다면 함수 호출 패턴은 무엇일까 ?

→ 이는 함수를 호출하는 방법으로 크게는 4가지가 있다.

  1. 함수 호출
  2. 메소드 호출
  3. 생성자 함수 호출
  4. apply/call/bind 호출

함수 호출

일반적으로 함수는 전역 객체에 바인딩 되어있기 때문에 this값은 window.

내부함수는 일반 함수, 메소드, 콜백함수 어디에서 선언되었든 관게없이 this는 전역객체를 바인딩한다.

메소드 호출

함수가 객체의 프로퍼티 값이면 메소드로서 호출된다.

이때 메소드 내부의 this는 해당 메소드를 소유한 객체, 즉 해당 메소드를 호출한 객체에 바인딩된다.

생성자 함수 호출

생성자 함수는 보통 생성 연산자 new를 사용하여 선언한 함수의 경우를 말하는데,

이 경우에는 생성자 함수의 코드가 실행되기 전 빈 객체가 생성된다. 이 빈 객체가 생성자 함수가 새로 생성하는 객체이다. 이후 생성자 함수 내에서 사용되는 this는 이 빈 객체를 가리킨다.

그리고 생성된 빈 객체는 생성자 함수의 prototype 프로퍼티가 가리키는 객체를 자신의 프로토타입 객체로 설정한다.

apply, call, bind

이 3가지 메서드는 원하는 this값을 바인딩 해줄 수 있는 메서드로

call은 보통 함수와 똑같이 인자를 넣고, apply는 인자를 하나로 묶어 배열로 만들어 넣는다.

var Person = function (name) {
  this.name = name;
};

var foo = {};

Person.apply(foo, [1, 2, 3])
Person.call(foo, 1, 2, 3)
Person.bind(foo)(1, 2, 3)

→ call, apply는 변화된 함수를 return 해주기때문에 뒤에 인자에 대한 값이 필요(call은 열거형, apply는 배열)

→ 대신 bind는 this값만 변경시켜주고 return하는것이 없기 때문에, 인자는 this를 대체할값만 필요.

apply() 메소드의 대표적인 용도는 arguments 객체와 같은 유사 배열 객체에 배열 메소드를 사용하는 경우이다. arguments 객체는 배열이 아니기 때문에 slice() 같은 배열의 메소드를 사용할 수 없으나 apply() 메소드를 이용하면 가능하다.


이번 글은 연결된 내용이 많다보니 길고 조금은 두서없이 적은 감이 없지 않아 있다.
여러번 읽게 될테니 읽으면서 더 정리하는게 좋겠다.

profile
생각하며 코딩하려고 노력하는 개발자가 되겠습니다.

0개의 댓글