part1
제로초의 Node.js 교과서 섹션 1 요약
2.1 호출 스택, 이벤트 루프
호출 스택은 실행 컨텍스트와 밀접한 연관이 있으며, 이벤트 루프와 더불어 JS의 동작 방식을 이해하는 데 필수적으로 공부해야 할 개념이다.
2.1.1 호출 스택
function first() {
second();
console.log("첫 번째");
}
function second() {
third();
console.log("두 번째");
}
function third() {
console.log("세 번째");
}
first();
위 코드의 실행 결과는 아래와 같이 예측할 수 있다.
세 번째
두 번째
첫 번째
함수의 호출과 실행을 호출 스택과 연관지어 생각해보면, 다음과 같은 과정을 거친다고 생각할 수 있기 때문이다.
- first() 호출, second() 호출 코드 실행
- second() 호출, third() 호출 코드 실행
- third() 호출, console.log() 실행 -> third()의 실행 끝
- second() 내부의 console.log() 실행 -> second()의 실행 끝
- first() 내부의 console.log() 실행 -> first()의 실행 끝
단, 실제 호출 스택 내부에서 최하단에 위치한 것은 '전역 실행 컨텍스트'에 해당하는 'Anonymous'가 위치한다. (크롬 브라우저 기준)
과정을 살펴보면 각 함수 호출 순서의 역순으로 실행되는 것을 볼 수 있으며, 호출 스택에서 실행이 완료된 함수는 스택에서 제거된다.
함수의 실행 컨텍스트 생성과 제거 과정이 LIFO와 동일하게 이루어지기 때문에 호출 '스택'이라고 불리는 것.
그런데 아래와 같은 비동기 코드는 호출 스택의 개념만으로는 설명이 어렵다.
function run() {
console.log("3초 후 실행");
}
console.log("시작");
setTimeout(run, 3000);
console.log("끝");
단순 직관으로 해석하면 실행 결과는 아래와 같지만,
시작
3초 후 실행
끝
실제 실행 결과는 다음과 같기 때문이다.
시작
끝
3초 후 실행
이러한 비동기 코드의 실행을 설명할 때는 '이벤트 루프'의 개념도 활용해야한다.
2.1.2 이벤트 루프
이벤트 루프는 자바스크립트가 싱글 스레드 기반의 언어이면서 비동기 작업을 수행할 수 있는 그 핵심적인 이유에 해당한다. 일단 모든 함수는 호출되면 호출 스택에 실행 컨텍스트를 적재하지만, 특정 함수들의 실행 컨텍스트는 Web API(백그라운드 등)에 의해 병렬 처리된다. Web API는 브라우저에 의해 제공되며, 실행 컨텍스트의 처리가 완료되면 Web API는 콜백 함수를 그 종류에 따라 마이크로 태스크 큐, 또는 매크로 태스크 큐 등으로 전달한다. 그리고 여기서 이벤트 루프가 해당 큐들에 적재된 콜백 함수들을 호출 스택이 전부 비었을 때 우선순위대로 끌어오는 것이다.
이벤트 루프에 대한 기본 개념은 이 정도만 알고 있어도 충분할 것 같다.
추가로, 태스크 큐에서 Promise
의 후속 처리 메서드와 process.nextTick
이 타이머 함수들에 비해 높은 우선순위를 갖는다는 것을 알아두자.
2.2 ES2015+ (ES6+)
2.2.1 const, let
ES6 문법부터 변수를 선언할 때 const(const는 상수 선언에 사용된다)와 let의 사용이 가능해졌다. ''사용이 가능해졌다"고 표현했지만, 사실상 대체하는 문법이다. 기존 (ES5 이전)에 사용하던 var 키워드로 선언된 변수와 어떤 차이점을 갖는지 알아보자.
- 블록 스코프를 갖는다. (var은 함수 스코프)
if (true) {
var x = 3;
}
console.log(x); // 3
if (true) {
const y = 3;
}
console.log(y); // Uncaught ReferenceError: y is not defined
위 예제 코드에서 알 수 있듯, var 변수 x는 함수 스코프를 갖기 때문에 if, for, while 등이 갖는 블록 스코프 외부의 위치에서도 참조하거나 변경될 수 있다. const 상수 y는 블록 스코프를 갖기 때문에, if가 갖는 블록 스코프 내에서만 참조할 수 있는 것이다.
이외에 const와 let이 갖는 특징은 다음과 같다.
- const : 재선언, 재할당 모두 불가능.
- let : 재선언 불가능, 재할당 가능.
2.2.2 템플릿 문자열 (템플릿 리터럴)
ES6부터는 템플릿 리터럴의 사용이 가능해져 문자열 내 동적인 값을 삽입하기 매우 쉬워 졌다.
// ES5 이전
var snackFee = "1000";
var statement1 = "이 과자는" + snackFee + "원 입니다.";
// ES6 이후
const statement2 = `이 과자는 ${snackFee}원 입니다.`;
2.2.3 객체 리터럴
ES5 시절의 객체 표현 방법은 다음과 같았다고 한다.
var sayNode = function () {
console.log("Node");
};
var es = "ES";
var oldObject = {
sayJS: function () {
console.log("JS");
},
sayNode: sayNode,
};
oldObject[es + 6] = "Fantastic";
oldObject.sayNode(); // Node
oldObject.sayJS(); // JS
console.log(oldObject.ES6); // Fantastic
ES6에서 도입된 객체 리터럴 표현을 살펴보자.
const newObject = {
sayJS() {
console.log("JS");
},
sayNode,
[es + 6]: "Fantastic",
};
newObject.sayNode(); // Node
newObject.sayJS(); // JS
console.log(newObject.ES6); // Fantastic
객체의 멤버로 함수를 할당할 때 메서드에 :function 키워드를 생략할 수 있는 점, 멤버의 키와 값의 내용이 동일할 때 하나를 생략할 수 있는 점, 구조 내부에서 멤버의 키를 지정할 때 [변수 + 값]과 같이 동적인 방식으로 구성해줄 수 있는 점 등이 객체를 선언할 때 매우 큰 편의성을 가져다 주었다.