본문 바로가기

JavaScript/[책] 코어 자바스크립트 -ing

02. 실행 컨텍스트 in JavaScript

00. 실행 컨텍스트 들어가기 전에..

- Stack: FILO(First In Last Out), LIFO(Last In First Out)

- Queue: FIFO(First In First Out), LILO(Last In Last Out)

 

제일 먼저 들어왔다가 제일 마지막에 빠지는 것을 stack 이라 하고

코드 실행에 관여하는 스택을 '콜 스택(Call Stack)' 이라고 한다.

콜스택: 현재 어떤 함수가 동작중인지, 다음에 어떤 함수가 호출될 예정인지 등을 제어하는 자료구조

 

근데 실행 컨텍스트가 뭐길래 이렇게 한 챕터를 할애하는 걸까? 왜 알아야만 하는 걸까?

실행 컨텍스는 scope, hositing, this, function, closure 등의 개념의 동작원리를 담고 있는 자바스크립트의 핵심 원리이다.

이걸 이해해야만 코드 독해를 할 수 있고, 그에 따라 디버깅도 쉬워진다.

가끔, 내가 예상하지 못한 값이 나올 때가 있었는데 실행 컨텍스트에 대한 개념이 좀 부족했던 것 같다.

차근차근히 하나씩 짚어가보자

 

 

 

01. 실행 컨텍스트란?

- 실행 컨텍스트(Execution Context): 실행할 코드에 제공할 환경 정보들을 모아놓은 객체.

동일한 조건 / 환경을 지니는 코드 뭉치를 실행할때 필요한 배경이 되는 조건 / 환경 정보

정의를 읽고 나니 뭔말인지 잘 모르겠...

찬찬히 읽어보니 다음과 같은 의문이 든다.

 

동일한 조건, 동일한 환경인 경우는 무엇일까? 그리고 실행 컨텍스트는 언제 생성되는 것일까? 실행 컨텍스트가 실행될 때 무슨일이 벌어지는가?

 

- 자바스크립트 에서는 동일한 환경, 조건을 가질 수 있는 경우는 다음과 같다.

1) 전역공간: 최상단의 공간(전역 공간)은 코드 내부에서 별도의 실행 명령이 없어도 브라우저에서 자동으로 실행하므로 자바스크립트 파일이 열리는 순간 전역 컨텍스트가 활성화되고 전체 코드가 끝날 때 전역 컨테스트 종료된다.

2) eval() 함수: 위험한 명령어.. 나는 일단 모르니깐 쓰지 말자.(찾아보니깐 아예 쓰지 말라고 하네... https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/eval)

3) module: import 되는 순간에 그 module 내부에 있는 컨텍스트가 생성되고 모듈 코드가 전부 끝났을 때 컨텍스트 종료,(아직 모듈도 익숙치 않으니 패스하고..)

4) 함수: 자바스크립트의 독립된 코드뭉치라고 할 수 있는 것은 곧 함수라고 볼 수 있다.

 

- 그렇다면 실행 컨텍스트 언제 생성될까?

우리가 흔히 실행 컨텍스트를 구성하는 방법은(실행 컨텍스트가 생성되는 시점은) 함수를 실행할 때이다.

 

- 실행 컨텍스트가 실행될 때 무슨 일이 벌어지는가? 

1) 동일한 환경에 있는 코드들을 실행할 때 필요한 환경 정보들을 모아 실행 컨텍스트 객체를 구성한 후에, 

2) 이를 콜 스택(call stack)에 쌓아 올렸다가

3) 가장 위에 쌓여 있는 컨텍스트와 관련 있는 코드들을 실행(코드의 환경과 순서를 보장)한다.

 

- 실행 컨텍스트 객체에는 어떤 정보들이 포함되는 것일까? (실행 컨텍스트의 구조)

출처: https://medium.com/crocusenergy/js-%EC%8B%A4%ED%96%89-%EC%BB%A8%ED%85%8D%EC%8A%A4%ED%8A%B8-2b8ab8da4f4

1) VariableEnvironment: 선언 시점의 LexicalEnvrionment의 스냅샷으로, 변경 사항은 저장되지 않음

· envrionmentRecord (snapshot): 현재 컨텍스트 내의 식벽자들에 대한 정보

· outerEnvironmentReference (snapshot): 외부 환경 정보

2) LexicalEnvironment: 처음에는 VariableEnvrionment와 같지만 변경 사항이 실시간으로 반영

· environmentRecord: 현재 컨텍스트 내의 식벽자들에 대한 정보(호이스팅)

· outerEnvironmentReference: 외부 환경 정보(스코프 체인)

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

 

02. VariableEnvironment

VariableEnvrionment에 담기는 내용은 LexicalEnvironment와 같지만 최초 실행 시의 스냅샷을 유지한다는 점이 다르다.

실행 컨텍스트 실행시 LexicalEnvironment를 주로 활용하게 된다.

 

주로 LexicalEnvrionment를 사용한다면.. 그렇다면 VariableEnvrionment가 굳이 왜 필요한거지 ?? 

(https://www.inflearn.com/questions/359757 비슷한 질문이 있어서 참고해보자)

 

 

 

03. LexicalEnvironment

- environmentRecord와 호이스팅

environmentRecord에는 현재 컨텍스트와 관련된 코드의 식별자 정보,

  1) 컨텍스트를 구성하는 함수에 지정된 매개변수 식별자

  2) 선언한 함수가 있을 경우 그 함수 자체

  3) var로 선언된 변수의 식별자

들이 저장된다. 

environmentRecord는 현재 실행된 컨텍스트 대상 코드 내에 어떤 식별자들이 있는지에만 관심이 있고, 각 식별자에 어떤 값이 할당될 것인지는 관심이 없음

 

여기서 호이스팅이라는 개념이 등장하는데,

호이스팅이란 식별자 정보를 컨텍스트 맨위로 끌어올린다 라고 생각하면 된다.

 

// 원본 코드
function a (x) { // 수집 대상1 (매개 변수)
  console.log(x);
  var x;         // 수집 대상2 (변수 선언)
  console.log(x);
  var x = 2;     // 수집 대상3 (변수 선언)
  console.log(x);
}

a(1);
// 주의! 실제 엔진은 이러한 방식으로 구동하지 않음
function a (x) {
  var x; // 매개변수 식별자
  var x; // var로 선언한 식별자
  var x; // var로 선언한 식별자
  
  x = 1;
  console.log(x); // 1
  console.log(x); // 1
  x = 2; 
  console.log(x); // 2
}

a(1); // 1 1 2

 

※ 그래도 제대로 알고가자!!

자바스크립트 엔진이 실제로는 식별자 정보를 끌어올리지는 않는다.

그렇지만 자바스크립트 엔진은 실행 컨텍스트가 관여할 코드들이 실행되기 전에 이미 해당 환경에 속한 코드의 변수명들을 모두 알고 있기 때문에

실제로는 끌어올리지는 않더라도 '자바스크립트 엔진은 식별자들을 최상단으로 끌어올려 놓은 다음 코드를 실행한다' 라고 생각하더라도 코드를 해석하는데 문제가 될 것이 없다.

호이스팅이란 변수 정보를 수집하는 과정을 더욱 이해하기 쉬운 방법으로 대체한 가상의 개념임을 알고 있자!

(변수는 선언부와 할당부를 나누어 선언부만 끌어올리는 반면 함수 선언은 함수 전체를 끌어올린다.)

 

environmentRecord가 저장하는 2) 선언한 함수가 있을 경우 그 함수 자체 경우는 호이스팅이 어떻게 될까?

함수의 경우 함수 선언은 전체를 끌어올리고 함수 표현식은 변수 선언부만 호이스팅 한다.

???? 읭 뭐라구 ???? 진짜 갈수록 헷갈린다....

 

일단 함수 선언식과 함수 표현식이 뭔지 부터 알아보자!

 

함수 선언문과 함수 표현식

함수 선언문: function 정의부만 존재하고 별도의 할당 명령이 없는 것, 반드시 함수명이 정의되어야 함

함수 표현식: 정의한 function을 별도의 변수에 할당하는 것. 일반적으로 익명 함수 표현식을 말함

 

함수를 정의하는 3가지 방식

// 함수 선언문, 함수명 aaa가 곧 변수명
function aaa () {
  //...
}
aaa(); // 실행 Okay

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

// 기명 함수 표현식 변수명은 c, 함수명은 bbb
var c = function bbb () {
  //...
}
c(); // 실행 Okay
d(); // 에러!!!

 

정의를 알아봤으니 그렇다면 이제 함수 선언문과 함수 표현식의 호이스팅을 비교해보자!

// 원본 코드
console.log(sum(1, 2)); // 3
console.log(multiply(3, 4)); // multiply is not a function

function sum (a, b) { // 함수 선언문
  retun a + b;
}

var multiply = function (a, b) { // (익명) 함수 표현식
  return a * b;
}
// 호이스팅된 코드
function sum (a, b) { // 함수 선언문은 호이스팅 시 함수 전체를 끌어 올림
  retun a + b;
}
var multiply; // 함수 표현식은 호이스팅 시 변수로 취급되어 선언 부분만 끌어 올림

console.log(sum(1, 2)); // 3
console.log(multiply(3, 4)); // multiply is not a function

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

함수 선언문은 호이스팅 시 함수 전체를 끌어 올리고

함수 표현식은 호이스팅 시 변수로 취급되어 선언 부분만 끌어 올린다.

 

- 함수 선언문의 위험성

함수 선언문이 코드 상단에 있는 선언되어 있는 상태에서 동일한 이름의 함수 선언문이 코드 하단에 중복해서 선언될 경우에 호이스팅 과정에서 두 함수 선언문이 모두 끌어 올려지고, 상대적으로 하단에 있는 선언문이 코드 전체에 영향을 미치게 된다.

 

함수 표현식으로 함수를 사용한다면 하단의 함수가 나오기 전까지는 상단에 선언 된 함수로 사용되다가 하단의 함수가 나오면 그때부터 하단의 함수로 적용된다.

 

예제를 통해서 살펴 보자!

// 함수 선언문
console.log(sum(3,4));

function sum(x, y) {
  return x + y;
}

var a = sum(1, 2);
console.log(a);

function sum(x ,y) {
  return `${x} + ${y} = ${x + y}`;
}

var c = sum(1, 2);
console.log(c);
// 호이스팅된 함수 선언문 코드
function sum(x, y) {
    return x + y;
}
var a;
function sum(x, y) {
    return `${x} + ${y} = ${x + y}`;
}
var c;

console.log(sum(3,4)); // 3 + 4 = 7
a = sum(1,2);
console.log(a); // 1 + 2 = 3
c = sum(1,2);
console.log(c); // 1 + 2 = 3

 

상대적으로 함수 표현식이 안전한데 코드로 살펴보자

// 함수 표현식 
console.log(sum(3, 4));

var sum = function (x, y) {
  return x + y;
};

var a = sum(1, 2);

console.log(a);

var sum = function (x, y) {
  return `${x} + ${y} = ${x + y}`;
}

var c = sum(1, 2);
console.log(c);
// 호이스팅된 함수 표현식
var sum;
var a;
var sum;
var c;

console.log(sum(3, 4)); // Uncaught Type Error

sum = function (x, y) {
  return x + y;
};

a = sum(1, 2);
console.log(a); // 3

sum = function (x, y) {
  return `${x} + ${y} = ${x + y}`;
};

c = sum(1, 2);

console.log(c) // 1 + 2 = 3

 

- 스코프, 스코프 체인, outerEnvrionmentReference

스코프(scope): 식별자에 대한 유효범위(오직, 함수에 의해서만 스코프가 생성(ES5))

 

스코프 체인: '식별자의 유효범위'를 안에서부터 바깥으로 차례로 검색해나가는 것을 의미한다. 

스코프 체인을 가능케 하는 것이 바로 LexicalEnvironment의 두번째 수집 자료인 outerEnvironmentReference이다.

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

 

outerEnvrionmentReference: 연결리스트 형태를 띈다. 

자신이 선언된 시점의 LexicalEnvrionmet만 참조하고 있으므로 가장 가까운 요소부터 차례대로만 접근할 수 있고 다른 순서로 접근하는 것은 불가능하다. 

이런 구조적 특성 덕분에 여러 스코프에서 동일한 식별자를 선언한 경우에는 무조건 스코프 체인 상에서 가장 먼저 발견된 식별자에만 접근 가능하다. 

 

가장 먼저 찾아진 식별자에만 접근가능하고 상위 컨텍스트 선언된 동일한 식별자에 접근할 수 없는 개념을 변수 은닉화 (variable shadowing)이라고 한다.

 

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;
var outer;
a = 1;
outer = function () {
    var inner;
    inner = function () {
        var a;
        console.log(a); // undefined
        a = 3;
    }
    inner();
    console.log(a); // 1
}
outer();
console.log(a); // 1

 

- 전역변수와 지역변수

ㅋㅋㅋ 당연하게도 전역 공간에서 선언한 변수는 전역변수이고,

함수 내부에서 선언한 변수는 무조건 지역변수이다.

 

 

 

04. this

실행 컨텍스트의 thisBinding에는 this로 지정된 객체가 저장된다.

실행 컨텍스트 활성화 당시에 this가 지정되지 않은 경우 this에는 전역 객체가 저장된다.

 

 

 

요약

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

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

- LexicalEnvrionment는 매개변수명, 변수의 식별자, 선언한 함수의 함수명 등을 수집하는 environmentRecord와 바로 직전 컨텍스트의 LexicalEnvironment 정보를 참조하는 outerEnvironmentReference로 구성되어 있다.

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

- 스코프는 변수의 유효범위를 말한다.

- outerEnvrionmentReference는 해당 함수가 선언된 위치의 LexicalEnvrionment를 참조한다.

 

 

 

추가로 더 공부하면 좋을 것

기명 함수 표현식: '내부 함수 이름'을 사용해 언제든 함수 표현식 내부에서 자기 자신을 호출 할 수 있다(예제는 https://ko.javascript.info/function-object 여기서 참조!)

ES5에서는 함수에 의해서만 스코프가 생성되고 ES6에서는 블록에 의해서도 스코프 경게가 발생함. 그러나 이러한 블록은 var로 선언한 변수에는 작용하지 않고 오직 새로 생긴 let 과  const, class, strict mode에서의 함수 선언 등에 대해서만 범위로서 역할을 수행한다.

 

 

 

스터디 회고

책읽을 시간이 충분해서 좋았다. 실행 컨텍스트에 대한 개념이 없어서 읽고 이해하는데 오래 걸렸는데 충분한 시간을 주셔서 1회 완독했다. 1년 전에 실행컨텍스트라는 개념을 살짝 들었는데 영어도 넘 많고 뭔소리인지 몰라서 그냥 이해 안하고 읽지도 않고 넘어갔다. 그래도 스터디를 통해서 또 블로그 글 정리를 통해서 관련 내용을 천천히 4번 이상 읽으니깐 개념이 좀 잡힌다. ㅠㅠ 역시 반복이 답이구나. 나는 머리는 좋지 않지만 그래도 남들보다 더 노력하면 되긴 되는구나 자신감이 좀 생겼다. 실행컨텍스트는 객체이고 그 객체안에 어떤 정보들이 들어가는지 예제 코드를 가지고 상상하고 실제 들어간 정보들과 비교해보면 재미있다. 호이스팅이란 개념이 가상의 개념이긴 하지만 자바스크립트 엔진이 실행될 코드가 실행되기 전에 관련 정보들을 다 알고 있다는 관점하에 호이스팅 개념을 써도 무방하다는 것도 알게되었다. 스코프란 개념도 어렴풋이 알고 있었는데 변수의 유효범위라는 멋진 단어를 얻어서 기분이 좋다. 

차분히 천천히 차근차근 하나씩 해가자. 중간에 못하면 잠깐 쉬어도 돼~ 멈추지말고 잘 쉬고, 그렇게 또 한 걸음씩 나가자!

'JavaScript > [책] 코어 자바스크립트 -ing' 카테고리의 다른 글

04. 콜백 함수 in JavaScript  (0) 2021.12.09
03. this in JavaScript  (0) 2021.12.02
01. 데이터 타입 in JavaScript  (1) 2021.11.19