코어자바스크립트 리뷰(2장 실행컨텍스트)

sanghun Lee·2020년 11월 30일
0

JavaScript

목록 보기
3/3

  • 본 포스팅은 코어자바스크립트의 책 내용의 일부를 발췌하여 정리한 것입니다. 개인 공부용이니 저작권법에 따라 스크립 또는 복사를 자제해주세요. (모든 글귀의 권리는 지은이 정재남님에게 있습니다!)😉

2. 실행컨텍스트

⇒실행할 코드에 제공할 환경정보들을 모아놓은 객체

  • 스택: 출입구가 하나뿐인 깊은 우물과 같은 데이터구조 (input: a,b,c,d → output: d,c,b,a)
  • 큐: 양쪽이 모두 열려있는 파이프, 통상적으로 한쪽은 입력만, 다른쪽은 출력만 담당 (input: a,b,c,d → output: a,b,c,d )

동일 환경에 있는 코드들을 실행할 때,

필요한 환경정보들을 모아 컨텍스트를 구성하고,

이를 콜스택에 쌓아올렸다가,

가장 위에 쌓인 컨텍스트와 관련있는 코드들을 실행하는 식,

으로 전체 코드의 환경과 순서를 보장한다.

여기서 동일한 환경(하나의 실행컨텍스트)을 구성할 수 있는 방법으로

  • 전역공간
  • eval()함수 (보통 자동생성됨)
  • 함수

등이 존재한다.

여기서 우리가 흔히 실행컨텍스트를 구성하는 방법은 함수를 실행하는 것 뿐이다.

var a = 1;
function outer(){
  function inner(){
    console.log(a)
    var a = 3;
  }
  inner();
  console.log(a);
}
outer();
console.log(a);
  1. empty stack
  2. 전역 컨텍스트 in stack(자바스크립트 코드 실행 시 담기게 된다, 쉽게 가장 넓은 스코프라고 생각)
  3. 전역컨텍스트 → outer ,in stack
  4. 전역컨텍스트 → outer → inner ,in stack
  5. 전역컨텍스트 → outer ,in stack
  6. 전역 컨텍스트 ,in stack

⇒ 한 실행 컨텍스트가 콜 스택의 맨 위에 쌓이는 순간이 곧 현재 실행할 코드에 관여하게 되는 시점으로 알 수 있다.


어떤 실행 컨텍스트가 활성화 될 때,

자바스크립트 엔진은 해당 컨텍스트와 관련된 코드들을 실행하는데 필요한 환경정보를 수집하여,

실행컨텍스트 객체에 저장한다.

  • 이 객체는 엔진이 활용할 목적으로 생성할 뿐이라 개발자가 코드를 통해 확인할 수는 없다

  • Variable Environment [= environmentRecord + outerEnvironmentReference(snapshot)]

    ⇒ 현재 컨텍스트내의 식별자들에 대한 정보 + 외부환경정보.

    선언시점의 Lexical Environment의 스냅샷으로 변경사항은 반영되지 않는다

  • Lexical Environment

    ⇒ 처음에는 Variable Env.와 같지만 변경사항이 실시간으로 반영된다

  • ThisBinding: this 식별자가 바라봐야 할 대상 객체.


environmentRecord & hoisting(호이스팅)

environmentRecord에는 현재 컨텍스트와 관련된 코드의 식별자 정보들이 저장된다.

컨텍스트 내부 전체를 처음부터 끝까지 훑어나가며 순서대로 수집한다

컨텍스트를 구성하는 함수에 지정된 매개변수 식별자, 선언한함수 그 자체(존재 시), var로 선언된 변수의 식별자 등이 위의 식별자에 해당한다.

  • 전역 실행 컨텍스트는 변수객체를 생성하는 대신 js 구동환경이 별도로 제공하는 객체인 전역객체(global object)를 활용한다.(ex. window, global(node.js)등이 존재한다) 이들은 내장객체가 아닌 hoist객체로 분류된다

실행 하기도 전에 변수정보를 모두 수집하며 간단하게 말해서

'자바스크립트 엔진은 식별자들을 최상단으로 끌어올려놓은 다음 실제 코드를 실행한다' 라고 생각하면 좋다.

호이스팅이라는 개념은 위의 이야기를 말하는 것이다

자바스크립트 엔진이 실제로 끌어올리지는 않으나 편의상 모든 식별자들을 최상단으로 올려놓은 다음 실행한다는 추상적 개념을 말하는 것.


함수선언문과 함수 표현식

function a (){} // 함수명 a가 곧 변수명 => 함수선언문
a(); // 실행된다

var b = function(){} // (익명)함수표현식. 변수명 b가 곧 함수명.
b(); // 실행된다

var c = function d(){} // 기명 함수 표현식. 변수명은 c 함수명은 d.
c(); //실행된다
d(); // 에러가 발생

기명 함수표현식은 함수 외부에서 함수명을 통해 호출이 불가, 함수내부에서 재귀 호출의 용도로는 사용가능

근데 굳이 써야할 필요가 있을까 ?

  • 기명함수식 썻던 이유

⇒(과거 익명함수 표현식이 undefined와 unnamed값이 나왔기 때문이란다.) 그래서 디버깅을 할 때 어떤함수인지 추적을 위해서 기명함수식을 썻다고 하나

이제는 모든 브라우저들이 익명함수 표현식의 변수명을 함수의 'name'프로퍼티에 할당하고 있으니 익명함수식을 쓴다고한다.

hoisting 중 lexical environment에서 수집하는 두가지 정보 중 environmentRecord(초기 스냅샷과정)에서 발생하는 호이스팅을 살펴보자

예시

console.log(sum(1,2));
console.log(multiply(3,4));

function sum(a,b){
	 return a+b;
}

var multiply = function(a,b){
	return a*b;
}

위 예시가 hoisting 이 되었을 때를 보여주는 가상의 예시

var sum = function sum(a,b){
	return a+b
};
var multiply;

console.log(sum(1,2));
console.log(multiply(3,4));

multiply= function (a,b){
	return a * b;
}

hoisting시의 예시를 보면 console.log(multiply(3,4)); 는 'multiply is not a function' 이라는 구문을 맞이하게 될 것이다. 위에 변수로 선언을 하여 변수영역 데이터에는 저장이 되었겠으나 데이터영역에는 저장이 아직 되지 않은 상태라 가리키는 대상이 없기 때문이다.

가리키는 대상은 console.log() 아래에 존재하기 때문!


스코프, 스코프체인(outerEnvironmentReference)

스코프(Scope)란 식별자에 대한 유효범위이다.

예를들어 어떤 경계 A의 외부에서 선언한 변수는 A의 외부뿐만아니라 내부에서도 접근이 가능하다.

하지만 A의 내부에서 선언한 변수는 오직 A의 내부에서만 접근할 수 있다.

위와 같은 스코프의 개념은 자바스크립트에서는 ES5까지 특이하게도 전역공간을 제외하면 오직함수에 의해서만 스코프가 생성되었다.

ES6에서는 블록에 의해서도 스코프 경계가 발생하게 되었으며 다른언어와 스코프의 개념에대한 유사성이 커졌다고 한다.

재미있는 점은 var로 선언한 변수에 대해서는 작용하지 않고 오직 새로 생긴 let 과 const, class, strict mode에서의 함수선언 등에 대해서만 범위로서의 역할을 수행한다고 한다.

그래서 ES6에서는 둘을 구분하기 위해서 함수스코프 , 블록 스코프 라는 용어로 스코프에 대한 구분을 둔다.


스코프체인

outerEnvironmentReference는 현재 호출된 함수가 선언될 당시의 LexicalEnvironment를 참조한다.

선언하다라는 행위가 실제로 일어날 수 있는 시점이란,

콜 스택 상에서 어떤 실행 컨텍스트가 활성화된 상태일 때 뿐이라고 한다.

왜냐하면 어떤 함수를 선언(정의)하는 자체의 행위도 하나의 코드에 지나지 않으며, 모든 코드는 실행 컨텍스트가 활성화 상태일 때 실행되기 때문이다.

 Inner = VariableEnvironment + LexicalEnvironment

VariableEnvironment: environmentRecord(snapshot) + outerEnvironmentReference(snapshot)

LexicalEnvironment: environmentRecord + outerEnvironmentReference
초기화 과정에서는 VariableEnvironment === LexicalEnvironment
변경이 저장 되며 LexicalEnvironment은 변경됨

위 코드 블록을 보고 예시를 한번 보자

A함수 내부에 B함수를 선언, B 함수 내부에 C함수를 선언한 경우 함수 C의 outerEnvironmentReference는 함수 B의 LexicalEnvironment를 참조한다. 함수 B의 LexicalEnvironment에 있는 outerEnvironmentReference는 다시 함수 B가 선언되던 때(A)의 LexicalEnvironment를 참조하겠다.

이처럼 outerEnvironmentReference는 연결리스트(LinkedList)형태를 띄게 된다.

선언시점의 LexicalEnvironment를 계속 찾아 올라가면 마지막엔 전역 컨텐스트의 LexicalEnvironment가 있을 것이다.

또한 , 각 outerEnvironmentReference는 오직 자신이 선언된 시점의 LexicalEnvironment만 참조하고 있으므로 가장 가까운 요소부터 차례대로 접근할 수 있고 다른순서로 접근하는 것은 불가능한데 이런 구조적 특성 덕분에 여러스코프에서 동일한 식별자를 선언한 경우

무조건 스코프체인 상에서 가장 먼저 발견된 식별자에만 접근가능하게 되는 것이다.

이렇게 전역변수랑 지역변수와 연관된 개념을 어렴풋이 나마 이해할 수 있다

(사실 책에서 알려주는 예시 중 굉장한 것이 있는데 이건 다 적기가 좀 그렇다. 제본도아니고 ..)


this

실행컨텍스트의 thisBinding에는 this로 지정된 객체가 저장된다. 실행컨텍스트 활성화 당시에 this 가 지정되지 않은 경우 this에는 자동으로 전역객체가 저장된다. 그밖에는 함수를 호출하는 방법에 따라 this에 저장되는 대상이 다르다.


2단원 종합정리

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

실행컨텍스트는 전역공간에 자동으로 생성되는 1. 전역컨텐스트 와 2. eval 및 함수 3. 실행에 의한 컨텍스트 등이 있다.

실행컨텍스트 객체는 활성화 되는 시점에 VariableEnvironment, LexicalEnvironment, ThisBinding 세가지의 정보를 수집한다.

실행컨텍스트 생성 시에는 VariableEnvironment. 와 LexicalEnvironment.가 동일한 내용으로 구성되지만 LexicalEnvironment 는 함수 실행도중 변경되는 사항이 즉시 반영된다.

VariableEnvironment는 초기상태를 유지한다.

VariableEnvironment와 LexicalEnvironment는

매개변수명, 변수의 식별자, 선언한 함수의 함수명등을 수집하는 environmentRecord와

바로 직전 컨텍스트의 LexicalEnvironment 정보를 참조하는 outerEnvironmentReference로 구성되어 있다.

호이스팅이라는 것은 코드해석을 좀 더 수월하게 하기 위해 environmentRecord의 수집과정을 추상화한 개념으로 실행컨텍스트가 관여하는 코드집단의 최상단으로 이들을 끌어올린다고 해석하는 것이다.

변수선언과 값 할당이 동시에 이뤄진 문장은 선언부 만을 호이스팅하고 할당과정은 원래자리에 남게 된다.

여기서 함수 선언문과 표현식의 차이가 발생한다.

스코프는 식별자의 유효범위를 말하는데 outerEnvironmentReference는 해당 함수가 선언된 위치의 LexicalEnvironment를 참조한다.

코드상에서는 어떤 변수에 접근하려고 하면 현재 컨텍스트의 LexicalEnvironment를 탐색해서 발견되면 그 값을 반환하고 발견하지 못할 경우 다시 outerEnvironmentReference에 담긴 LexicalEnvironment를 탐색하는 과정을 거친다.

전역컨텍스트의 LexicalEnvironment까지 탐색해도 해당 변수를 찾지못하면 undefined를 반환한다.

전역컨텍스트의 LexicalEnvironment에 담긴 변수를 전역변수라 하고 , 그 밖의 함수에 의해 생성된 실행컨텍스트의 변수들은 모두 지역변수이다.

안전한 코드 구성을 위해서는 가급적 전역변수는 최소화 하자

this에는 실행컨텍스트를 활성화하는 당시에 지정된 this가 저장된다.

함수를 호출하는 방법에 따라 그값은 달라지며 지정되지 않은 경우에는 전역객체가 저장된다.

profile
알고리즘 풀이를 담은 블로그입니다.

0개의 댓글