저번에 포스팅한 "데이터 타입" 에 이어서 다음 챕터인 "실행 컨텍스트"를 포스팅 할 것이다.
https://ilikezzi.tistory.com/52
[Javascript] 데이터 타입_코어 자바스크립트
계획했던대로 자격증 취득도 다 해서, 8월부터는 알고리즘과 팀프로젝트에 전념할 계획이였다. 하지만 알고리즘 스터디를 하기전에 JS 기본기를 탄탄하게 다져두고 하는게 깊은 이해에 도움이
ilikezzi.tistory.com
위에 있는 저번 포스팅과 크게 이어지는 내용은 아니지만,
그래도 "데이터 타입"에 대한 기본적인 이해가 있어야 이번 포스팅을 쉽게 이해할 수 있을듯 싶다.
02. 실행 컨텍스트
실행 컨텍스트란?
- 실행 컨텍스트(Execution Context)
실행할 코드에 제공할 환경 정보들을 모아놓은 객체다.
자바스크립트의 동적 언어로서의 성격을 가장 잘 파악할 수 있는 중요한 개념이다.
기본적인 개념이지만 한번 집고 넘어가자.
스택(Stack)
- 출입구가 하나뿐인 깊은 우물 같은 데이터 구조다.
- 끝에서만 자료를 넣거나 뺄 수 있다.
- 앞서 실행 컨텍스트를 "실행할 코드에 제공할 환경 정보들을 모아놓은 객체"라고 했는데,
이를 콜 스택에 쌓아 올렸다가, 가장 위에 쌓여있는 컨텐스트와 관련 있는 코드를 하나씩 실행한다.
큐(Queue)
- 양쪽이 모두 열린 파이프 같은 데이터 구조다.
- 한 쪽 끝에서 넣고 반대 쪽 끝에서 빼는 식으로 동작한다.
// --------------------------- (1)
var a = 1;
function outer() {
function inner() {
console.log(a); // undefined
var a = 3
}
inner(); //--------------- (2)
console.log(a); // 1
}
outer(); // ------------------ (3)
console.log(a); // 1
코드의 콜스택 순서를 사진으로 보자.
1. 처음 JS 코드를 실행하는 순간(1) 전역 컨텍스트가 콜스택에 push 된다.
2. 콜스택에 쌓인 전역 콘텍스트 관련된 코드를 실행하다가 (3)에서 Outer를 만나 콜스택에 push 된다.
3. 이제 콜스택 맨 위에 Outer가 놓인 상태이므로 전역 콘텍스트 관련 코드는 일시 중지하고,
Outer 관련 컨텍스트, 즉 Outer 함수 내부의 코드를 순차적으로 실행한다.
4. 다시 (2)에서 Inner 함수의 실행 컨텍스트가 콜스택에 push되서, Inner 함수 내부코드를 실행한다.
5. Inner함수 실행이 끝나면 "pop" 되면서 콜스택에서 제거된다.
6. 이제 아래에 있던 Outer 함수코드를 실행하고, 끝나면 pop 되면서 콜스택에서 제거된다.
7. 마지막으로 맨 아래에 있던 전역 콘텍스트를 마저 실행시키고, 끝나면 pop 되면서 끝난다.
이 처럼 콜스택은 후입선출(LIFO) 방식으로 동작하게 된다.
또한 콜스택이 너무 깊어지면 스택 오버플로우라는 문제가 발생할 수 있다.
이제 활성화된 실행 컨텍스트에는 무엇이 담겨 있는지 보자.
- LexicalEnvironment
현재 코드의 실행 컨텍스트 내에서 식별자와 그에 해당하는 값이 어떻게 연결되는지 나타내는 환경이다.
쉽게 말하면, 어떤 변수가 어떤 값을 가지고 있는지 관리해주는 역할이다.
- VariableEnvironment
현재 실행 컨텍스트에서 var로 선언된 변수들을 관리하는 환경이다.
선언시점의 LexicalEnvironment의 스냅샷으로 변경사항은 반영X
ES6 이전에는 LexicalEnvironment와 거의 동일했지만,
let과 const가 도입되면서 LexicalEnvironment가 좀 더 복잡한 역할을 하게 됐다.
- ThisBinding
this 키워드의 값이 무엇인지 결정하는 부분이다.
함수가 어떻게 호출되었는지, 그리고 어떤 객체의 컨텍스트에서 실행되었는지에 따라 this의 값이 달라지게 된다.
Hoisting
function a() {
console.log(b); // (1)
var b = 'bbb'; // 수집 대상 1 (변수선언)
console.log(b); // (2)
function b() { } // 수집 대상 2 (함수선언)
console.log(b); // (3)
}
a();
해당 코드를 예시로 보고 하나씩 살펴보자.
1. a 함수를 실행하는 순간 a 함수의 실행컨텍스트가 생성된다.
2. 이때 변수명과 함수 선언정보를 위로 끌어올린다. (호이스팅)
- 변수는 선언부와 할당부로 나누어 선언부만 끌어올림
- 함수 선언은 함수 전체를 끌어 올린다.
function a() {
var b; // 수집 대상 1 (변수선언) 선언부만 끌어올린다.
// function b() { } // 수집 대상 2 (함수선언) 함수 선언은 전체를 끌어올린다.
var b = function b() { } // 변경부분!
console.log(b); // (1)
b = 'bbb'; // 변수의 할당부는 자리에 남는다.
console.log(b); // (2)
console.log(b); // (3)
}
a();
호이스팅이 이뤄지면 다음과 같이 변환된다.
하지만 3번째 주석 라인으로 처음에는 변환되지만, 4번째 라인과 같은 의미로 여겨진다.
왜냐?
함수 선언문을 사용하면 자바스크립트 엔진이 해당 함수를 변수에 할당하는 과정을 내부적으로 처리한다.
호이스팅이 끝난 상태에서의 함수 선언문은 함수명으로 선언한 변수에 함수를 할당한 것 처럼 여긴다.
이게 좀 복잡하게 느껴질 수 있는 부분인데, 이전 포스팅에서 데이터 메모리 할당을 봤으면 이해가 될 것이다.
따라서 각 콘솔에 대한 값은 이렇게 출력된다.
(1) : b함수 / (2) : 'bbb' / (3) : 'bbb'
"함수 선언문"과 "함수 표현식"
이제 호이스팅을 다루는데에 꼭 알아둬야 할 "함수 선언문"과 "함수 표현식"에 대해서 알아보자.
function a() {} // 함수 선언문. 함수명 a가 곧 변수명
a(); // 실행 OK
var b = function() {} // (익명) 함수 표현식. 변수명 b가 곧 함수명
b(); // 실행 OK
var c = function d() {} // (익명) 기명 함수 표현식. 변수명 c , 함수명 d
c(); // 실행 OK
d(); // 에러!
1. 함수 선언문 (Function Declaration)
함수 선언문은 "function" 키워드로 시작하고, 이름이 있는 함수를 선언한다.
호이스팅 - 함수 선언문은 코드가 실행되기 전에 메모리에 할당되어, 선언문이 있는 코드 블록 내 어디에서든 접근 가능하다.
이름 필수 - 함수에 반드시 이름이 있어야 한다.
2. 함수 표현식 (Function Expression)
함수 표현식은 변수에 함수를 할당하는 형태로 되어있다.
호이스팅 X - 변수 선언만 호이스팅되고, 함수는 할당 시점에 메모리에 저장돼. 따라서 선언 전에 호출하려고 하면 오류가 발생한다.
이름이 선택: 함수에 이름을 주지 않아도 된다. 이름을 준다면 디버깅에 도움이 될 수 있다.
차이점을 한번 더 정리 해보면 다음과 같다.
호이스팅: 함수 선언문은 전체 접근 가능, 함수 표현식은 선언 후에만 접근 가능.
선언 형태: 함수 선언문은 function 키워드로 시작, 함수 표현식은 변수 할당 형태.
이름 유무: 함수 선언문은 이름 필수, 함수 표현식은 이름 선택.
여기서 중요한 포인트가 있다.
협업을 할 때에 함수 선언문은 위험하다.
호이스팅을 제대로 이해한분들은 이게 무슨 얘기인지 눈치 챘을것이다.
두 개발자가 같은 이름으로 함수를 선언하게 되면 나중에 선언한 함수가 이전 선언을 덮어써버린다.
이로 인해 예상치 못한 동작이나 버그가 발생할 수 있다.
그리고 같은 이름의 함수가 여러 곳에서 선언되면, 나중에 코드를 유지보수하거나 리팩토링할 때 문제가 생길 수 있다.
어떤 함수가 어떻게 동작하는지 파악하기 어려워지고, 버그를 찾거나 수정하기 힘들어진다.
따라서 상대적으로 함수 표현식이 안전하다.
스코프 체인
이제 스코프 체인에 대해서 알아보자.
스코프 체인이란?
스코프 체인은 현재 실행 컨텍스트의 스코프에서 시작해서, 상위 스코프를 따라가며 체인처럼 연결된 구조를 말한다.
이 체인은 변수를 찾을 때 사용되며, 현재 스코프부터 시작해서 전역 스코프까지 이어진다.
작동 방식은 다음과 같다.
현재 스코프에서 찾기: 변수를 찾을 때, 현재 스코프부터 찾기 시작한다.
상위 스코프로 올라가기: 현재 스코프에 없으면, 상위 스코프로 올라가서 찾는다.
전역 스코프까지 반복: 전역 스코프까지 계속 올라가서 찾는다.
찾지 못하면 오류: 전역 스코프까지 갔는데도 못 찾으면 ReferenceError를 내뱉는다.
이 규칙을 이해하면 변수가 어떻게 조회되고 동작하는지 이해하는 데 도움이 된다.
스코프 체인 예제 코드로 보자.
var a = 1;
var outer = function () {
var inner = function() {
console.log(a); // undefined
var a = 3;
};
inner();
console.log(a); // 1
};
outer();
console.log(a); // 1
스코프 체인과 호이스팅 관점에서 동작순서를 보자.
1. 전역 스코프에서 변수 a 와 함수 outer가 선언된다.
2. 변수 a에 1을 할당한다.
3. outer 함수가 호출되어 실행 컨텍스트가 생성된다.
4. outer 내부에서 inner 함수가 선언된다.
5. inner 함수가 호출되고, 새로운 실행 컨텍스트가 생성된다.
이 때, inner의 지역 스코프에서 a의 선언이 호이스팅되어 undefined로 초기화된다.
6. inner 함수 내에서 a를 출력하려고 하면, 현재 스코프에서 a의 값은 undefined이므로, undefined가 출력된다.
7. inner 함수 내에서 지역 변수 a에 3을 할당한다.
8. inner 함수의 실행이 종료되고, 해당 실행 컨텍스트가 스택에서 제거된다.
9. outer 함수 내에서 전역 변수 a를 출력하려고 하면, 해당 스코프에서 a의 값은 1이므로, 1이 출력된다.
10. outer 함수의 실행이 종료되고, 해당 실행 컨텍스트가 스택에서 제거된다.
11. outer 함수 외부에서 전역 변수 a를 출력하면, 전역 스코프에서 a의 값은 1이므로, 1이 출력된다.
이렇게 스코프체인과 호이스팅 작동방식에 대해서 예제코드로 찝어보았다.
오늘은 내용이 좀 길었지만 개념과 코드의 동작방식을 하나씩 살펴보면
실행 컨텍스트에 대해서 확실히 이해를 할 수 있을 것이다!
다음 챕터는 "this" 이다.
'Programming Language > Javascript' 카테고리의 다른 글
[Javascript] console.dir()로 객체의 모든 depth 구조 출력 (0) | 2023.08.15 |
---|---|
[Javascript] new Set() 배열에서 쉽게 중복값 제거하기 (0) | 2023.08.14 |
[Javascript] 데이터 타입_코어 자바스크립트 (0) | 2023.08.07 |
[Javascript] null 병합연산자 "??" (0) | 2023.04.11 |
[Javascript] split() 지정 구분기호로 문자열을 배열로 분할 (0) | 2023.03.30 |