먼저 스택과 큐에 대해서 알아보자
📌 스택(stack): 출입구가 하나 뿐인 우물과 같은 데이터 구조이다.
abcd순서대로 들어가면, 꺼낼때는 d,c,b,a 순이다.
📌 큐(queue): 한쪽은 입력만, 다른 한쪽은 출력만 담당하는 구조이다. (양쪽 모두 입출력 되는 큐도 있음) abcd순으로 저장했다면 꺼낼 때는 abcd순이다.
📌 실행 컨텍스트: 실행할 코드에 제공할 환경 정보들을 모아 놓은 객체
📌 실행 컨텍스트를 구성하는 방법으로는 아래와 같다.
1) 전역공간
2) eval()함수
3) 함수
자동으로 생성되는 전역공간과 악마로 취급받는 eval 제외하면 흔히 실행컨텍스트를 구성하는 방법은 함수를 실행하는 것이다.
let a = 1; ----------------- (1)
function outer() {
function inner() {
console.log(a); // undefined
let a = 3;
}
inner(); ----------------- (3)
console.log(a); // 1
}
outer(); ----------------- (2)
console.log(a); // 1
📝 처음 자바스크립트 코드를 실행하는 순간
(1) 전역 컨텍스트가 콜 스택에 담기게 된다.
브라우저에서 자동으로 실행하므로 자바스크립트 파일이 열리는 순간 전역 컨텍스트가 활성화된다고 이해하면 된다.
(2) 전역컨텍스트와 관련된 코드 순차적 진행하다가 outer함수를 호출하면
js엔진은 outer에 대한 환경정보를 수집해서 outer 실행 컨텍스트를 생성한 후 콜스택에 담는다. 전역 컨텍스트 관련 코드는 일시중단된다.
(3) outer내부에서 다시 inner함수의 실행 컨텍스트가 콜스택 가장 위에 담기면 outer 컨텍스트와 관련된 코드의 실행을 중단하고 inner함수 내부의 코드를 순서대로 진행한다.
inner 실행컨텍스트가 종료되면 콜스택에서 제거 되고,
outer 컨텍스트가 이어서 실행된다, 마찬가지로 outer 실행이 종료되면 전역 컨텍스트가 실행되고, 그 마저 종료되면 콜스택에는 아무것도 남지 않는 상태로 종료된다.
이 객체는 js엔진이 활용할 목적으로 생성할 뿐, 개발자들은 코드를 통해 확인이 불가능하다.
담기는 정보들은 아래와 같다.
📌 variableEnvironment: 현재 컨텍스트 내의 식별자들에 대한 정보 + 외부 환경 정보, 선언 시점의 LexicalEnvironment의 스냅샷
📌 LexicalEnvironment: 처음에는 variableEnvironment와 같지만 변경 사항이 실시간으로 반영된다.
📌 ThisBinding: this 식별자가 바라봐야 할 대상 객체이다.
담기는 내용은 LexicalEnvironment 와 같지만 최초 실행 시의 스냅샷을 유지한다는 점이 다르다.
variableEnvironment, LexicalEnvironment 내부에는 environmentRecord와 outer-Environment로 구성되어 있다.
컨텍스트를 구성하는 환경 정보들을 사전에 접하는 느낌으로 모아 놓는 것이다.
environmentRecord에는 현재 컨텍스트와 관련된 코드의 식별자 정보들이 저장된다.
매개변수 식별자, 선언한 함수가 있을 경우 함수 자체, var로 선언된 변수의 식별자 등 식별자에 해당한다.
컨텍스트 내부 전체를 처음부터 끝까지 쭉 순서대로 수집한다.
코드가 실행도기 전임에도 불구하고 js엔진은 이미 해당 환경에 속한 코드의 변수명들을 모두 알고 있게 된다.
js엔진은 식별자들을 최상단으로 끌어올려놓은 다음에 실제 코드를 실행하게 된다.
여기서 끌어올리다 호이스팅의 개념이 등장한다.
environmentRecord에는 매개변수의 이름, 함수선언, 변수명이 담긴다.
function a(x) {
console.log(x); // undefined
var x;
console.log(x); // undefined
var x = 2;
console.log(x); // 2
}
a(1);
호이스팅이 되지 않았을 때는 출력 값이 위와 같다.
function a(x) {
var x = 1; // 수집 대상 1(매개변수 선언)
console.log(x);
var x; // 수집 대상 2 (변수 선언)
console.log(x);
var x = 2; // 수집 대상 3 (변수 선언)
console.log(x);
}
a(1);
매개변수를 변수 선언/할당과 같다고 간주하여 변환한 상태입니다.
function a(x) {
var x; // 수집 대상 1 (매개변수 선언)
var x; // 수집 대상 2 (변수 선언)
var x; // 수집 대상 3 (변수 선언)
x = 1; // 수집 대상 1의 할당
console.log(x); // 1
console.log(x); // 1
x = 2; // 수집 대상 3의 할당
console.log(x); // 2
}
a(1);
출력 결과물이 즉 1,1,2로 나오게 된다.
함수선언에 대한 예제에 대해서 알아보자
function a() {
console.log(b); // undefined
var b = 'bbb';
console.log(b); // 'bbb'
function b() {}
console.log(b); // 'bbb'
}
위와 같이 예상하는데 실제로는 과연??
function a() {
var b;
var b = function b() {} // 호이스팅이 끝난상태에 함수선언문은 변수에 할당한것 처럼
console.log(b); // function b
b = 'bbb';
console.log(b); // 'bbb'
console.log(b); // 'bbb'
}
a함수를 실행하는 순간 a 함수의 실행 컨텍스트가 생성된다.
이때 변수명과 함수선언의 정보를 위로 끌어올린다.
변수는 선언부와 할당부로 나누어 선언부만 끌어오리며,
함수선언은 함수 전체를 끌어올린다.
호이스팅이 끝난 상태에서의 함수표현식은 함수명으로 선언한 변수에 함수를 할당한 것 처럼 여길 수 있다.
var, const, let에 대해서 알아보자
💣 const,let도 호이스팅이 된다고 하는데 실제로는 reference error가 발생한다.
그 이유는 무엇일까??
자바스크립트에서 변수는 위와 같이 선언,초기화,할당 3가지 단계를 거쳐서 생성되게 된다.
선언: 변수를 실행 컨텍스트의 변수 객체에 등록한다.
초기화: 변수를 위한 메모리를 만드는과정, 할당된 메모리에는 undefined 로 초기화한다.
할당: undefined로 할당된 변수에 다른값을 할당한다.
var는 선언과 초기화가 동시에 발생하지만, const/let은 선언과 초기화가 분리되어 진행되어 아직 메모리 할당이 되지 않아 reference error가 발생하는 것이다.
💣 TDZ구간 즉 사각지대에 걸리게 되어 메모리가 할당되지 않아 참조에러가 발생
📌 함수 선언문
function 정의부만 존재하고 별도의 할당 명령이 없는 것, 함수명이 정의되야한다.
function a() {}
📌 함수 표현식
정의한 function을 별도의 변수에 할당하는 것이다.
함수명을 정의한 함수를 기명 함수 표현식, 정의 하지 않는 것을 익명함수 표현식으로 한다.
일반적으로 익명함수 표현식을 말한다.
let b = function() {} // 익명함수 표현식
let c = function d() {} // 기명함수 표현식
⏰ 과거에는 기명함수 표현식을 많이 사용했었는데, 그 이유는 디버깅시에 익명 함수 표현식은 함수명이 undefined, unnamed라고 나왔었다.
그러나 현재 모든 브라우저들은 익명함수 표현식의 변수명을 함수의 name 프로퍼티에 할당하고 있다. 굳이 기명함수 사용하지 않아도 된다.
📌 함수선언식은 js로딩시점에 변수객체에 함수를 할당하기 때문에 변수객체에 너무 많은 코드를 저장하면 어플리케이션 응답속도가 느려질 수 있다.
그러나 함수 표현식은 runtime시점에서 실행되기 때문에, 함수 표현식 권장!
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;
}
이러한 코드가 있다.
호이스팅 되면 아래와 같다.
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;
}
📌 스코프 (Scope)
식별자에 대한 유효범위입니다.
전역공간을 제외하면 오직 함수에 의해서만 스코프가 생성된다.
📌 스코프 체인 (Scope Chain)
식별자의 유효범위를 안에서 부터 바깥으로 차례로 검색해나가는것을 의미한다.
이를 가능케 하는것이 바로 lexicalEnvironment의 두번째 수집 자료인 outerEnvironmentReference이다.
예를 들어 A함수 내부에 B함수를 선언하고 다시 B 함수 내부에 C함수를 선언한 경우,
함수 C의 outerEnvironmentReference는 함수 B의 LexicalEnvironment를 참조하게 된다.
함수 B의 LexicalEnvironment에 있는 outerEnvironmentReference는sms A의 LexicalEnvironment를 참조하게 된다.
이와 같이 outerEnvironmentReference는 연결 리스트 형태를 띈다.
선언시점의 LexicalEnvironment만 참조하고 있으므로 가장 가까운 요소부터 차례대로 접근할 수 있고 다른 순서로 접근하는 것은 불가능하다.
무조건 스코프 체인 상에서 가장 먼저 발견된 식별자에만 접근 가능하다.
📌 실행 컨텍스트 : 실행할 코드에 제공할 환경정보들을 모아 놓은 객체
전역 공간에서 자동으로 생성되는 전역 컨텍스트와 eval및 함수 실행에 의한 컨텍스트 등이 있다.
📌 실행 컨텍스트 객체가 활성화되는 시점에 variableEnvironment, LexicalEnvironment, ThisBinding의 세가지 정보를 수집한다.
📌 실행 컨텍스트 생성할 때 variableEnvironment, LexicalEnvironment 는 동일한 내용으로 구성되지만 LexicalEnvironment 는 함수 실행 도중에 변경되는 사항이 즉시 반영된다.
variableEnvironment는 초기상태를 유지한다.
📌 variableEnvironment,LexicalEnvironment는 매개변수명, 변수의 식별자, 선언한 함수의 함수명 등을 수집하는 environmentRecord와 바로 직전 컨텍스트의 LexicalEnvironment 정보를 참조하는 outerEnvironmentReference로 구성된다.
📌 호이스팅은 코드를 좀 더 수월하게 해석하기 위해 environmentRecord의 수집 과정을 추상화한 개념으로, 실행 컨텍스트가 관여하는 코드 집단의 최상단으로 이들을 끌어올린다고 해석하는 것이다.
선언부만 호이스팅하고 할당과정은 원래 자리에 남아있게 되는데, 여기서 함수선언문과 함수표현식의 차이가 발생한다.
📌 스코프는 변수의 유효범위를 의미한다.
outerEnvironmentReference는 해당 함수가 선언된 위치의 LexicalEnvironment 참조한다. 코드상에서 어떤 변수에 접근하려고 하면 현재 컨텍스트의 LexicalEnvironment를 탐색하여 발견되면 그값 반환하고 발견하지 못한 경우 outerEnvironmentRecord에 담긴 LexicalEnvironment를 탐색하는 과정을 거치게 된다.
전역 컨테스트의 LexicalEnvironment 까지 탐색해도 해당 변수를 찾지 못하면 undefined를 반환한다.
📌 전역변수: 전역 컨텍스트의 LexicalEnvironment에 담긴 변수이다.
📌 지역변수: 함수에 의해 생성된 실행 컨텍스트의 변수이다.