Node.js란?
웹 서버의 개념이 아닌 Javascript로 서버를 구축하고 서버에서 Javascript가 작동되도록 해주는 런타임 환경(플랫폼)을 말한다.
여기서 런타임(Runtime)이란, 프로그래밍 언어가 구동(실행)되는 환경을 말한다.
npm
NPM은 (Node Package Manger)의 약자로 명령어로 자바스크립트 라이브러리를 설치하고 설치된 라이브러리의 버전을 관리해주는 패키지 매니저다. 개발자는 단 몇줄의 명령어로 기존의 공개된 모듈들을 설치하고 활용할 수 있다.
NPM을 사용하면 얻는 장점
관리 용이
설치용이
{
"name": "mustard_seed",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"@types/jest": "^27.5.2",
"@types/node": "^16.18.1",
"@types/react": "^18.0.23",
"@types/react-dom": "^18.0.7",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-icons": "^4.6.0",
"react-router-dom": "^6.4.2",
"react-scripts": "5.0.1",
"typescript": "^4.8.4",
"web-vitals": "^2.1.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^5.41.0",
"@typescript-eslint/parser": "^5.41.0",
"eslint": "^8.22.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-react": "^7.31.10",
"eslint-plugin-react-hooks": "^4.6.0",
"prettier": "^2.7.1"
}
}
var, let, const 정의와 차이점
변수의 선언은 var
, const
, let
키워드로 할 수 있으며, ES6에서 const와 let이 추가되었다.
자바스크립트에서 변수 선언은 선언 → 초기화
단계를 거쳐 수행된다.
var 키워드
var 키워드를 이용한 변수 선언은 선언 단계와 초기화 단계가 동시에 진행되어, kmj에 암묵적으로 undefined를 할당해 초기화한다.
var kmj
console.log(kmj) // output: undefined
그런데 반대로, console을 먼저 찍어도 반환 값이 undefined로 나온다.
console.log(kmj) // output: undefined
var kmj
이는 변수 선언이 런타임에서 되는 것이 아니라, 그 이전 단계에서 먼저 실행되기 때문이다. 자바스크립트 엔진은 소스코드를 한 줄씩 순차적으로 실행하기에 앞서, 변수 선언을 포함한 모든 선언문(ex. 변수 선언문, 함수 선언문 등)을 찾아내 먼저 실행한다. 즉, 변수 선언이 어디에 있든 상관없이 다른 코드보다 먼저 실행되는 특징을 호이스팅(hoisting)이라 한다.
변수 할당
변수에 값을 할당 할 때에는 할당 연산자(=)를 사용한다.
var kmj // 변수 선언
kmj = 'howdy-mj' // 값의 할당
var kmj = 'howdy-mj' // 변수 선언과 할당
변수 선언과 할당은 하나의 문(statement)으로 단축 표현할 수 있지만, 두 개의 실행 시점이 다르다. 변수 선언이 호이스팅되어 런타임 이전에 실행되지만, 값의 할당은 소스코드가 순차적으로 실행되는 런타임에 실행된다.
따라서 변수의 할당과 console을 실행하는 위치에 따라 반환되는 값이 다르다.
console.log(kmj) // output: undefined
var kmj = 'howdy-mj'
console.log(kmj) // output: howdy-mj
kmj라는 변수에 새로운 값을 재할당할 수도 있다.
console.log(kmj) // output: howdy-mj
kmj = 'mj'
console.log(kmj) // output: mj
재할당은 변수에 저장된 값을 다른 값으로 변경하는 것으로, 만약 변경할 수 없는 값이라면 이는 변수가 아니라 상수(constant)라 부른다.
var, let, const의 차이
앞에서 발견한 var
키워드의 문제점은 크게 세 가지가 존재한다.
ES6에서 나온 let
과 const
키워드는 위의 세 가지 문제점을 해결했다.
let
let 키워드로는 변수 중복 선언이 불가하지만, 재할당은 가능하다.
let name = 'kmj'
console.log(name) // output: kmj
let name = 'howdy' // output: Uncaught SyntaxError: Identifier 'name' has already been declared
name = 'howdy'
console.log(name) // output: howdy
const
const가 let과 다른 점이 있다면, 반드시 선언과 초기화를 동시에 진행되어야 한다.
const name; // output: Uncaught SyntaxError: Missing initializer in const declaration
const name = 'kmj'
const도 let과 마찬가지로 재선언이 불가하며, 더 나아가 재할당도 불가하다. 재할당의 경우, 원시 값은 불가능하지만, 객체는 가능하다. const 키워드는 재할당을 금지할 뿐, '불변'을 의미하지 않는다.
// 원시값의 재할당
const name = 'kmj'
name = 'howdy' // output: Uncaught TypeError: Assignment to constant variable.
// 객체의 재할당
const name = {
eng: 'kmj',
}
name.eng = 'howdy'
console.log(name) // output: { eng: "howdy" }
let, const 키워드로 선언한 변수는 모두 코드 블록(ex. 함수, if, for, while, try/catch 문 등)을 지역 스코프로 인정하는 블록 레벨 스코프를 따른다.
위 var 키워드로 예를 들었던 것을 그대로 가져와 바꾸면 아래와 같은 결과가 나온다.
let a = 1
if (true) {
let a = 5
}
console.log(a) // output: 1
var 키워드로 선언한 경우 5가 나왔지만, let 키워드로 선언한 경우 if 문 안에 있는 것은 지역 스코프를 가져 전역에서 console을 찍었을 경우, 전역에 있는 a가 결과 값으로 출력된다. (const 키워드도 let 키워드와 동일하게 동작한다)
‘==’와 ‘===’ 연산자의 차이는 무엇인지 설명해주실 수 있을까요?
Equality
==
와 !=
는 Equality 연산자이다. Equality라고 해봤자 사실 영어로는 그 뉘앙스가 제대로 와닿지 않는다. 중요한 점은 JavaScript에서 ==
를 사용하면 연산이 되기 전에 피연산자들을 먼저 비교할 수 있는 형태로 변환시킨다는 것이다.
따라서 ==
를 사용하면 다음과 같은 사실이 성립한다.
254 == '254' // return true
true == 1 // return true
undefined == null // return true
'abc' == new String('abc') // return true
null == false // return false
'true' == true // return false
true == 2 // return false
Identity
===
와 !==
는 Identity 연산자이다. 이 녀석은 ==
와는 반대로 형변환을 하지 않고 연산한다.
254 === '254' // return false
true === 1 // return false
undefined === null // return false
'abc' === new String('abc') // return false
비동기 통신
동기와 비동기의 차이점
동기(Synchronous: 동시에 일어나는)
동기는 말 그대로 동시에 일어난다는 뜻이다. 요청과 그 결과가 동시에 일어난다는 약속이다.
바로 요청을 하면 시간이 얼마나 걸리던지 요청한 자리에서 결과가 주어져야 한다.
순서에 맞춰 진행되는 장점이 있지만, 여러 가지 요청을 동시에 처리할 수 없다.
위 그림의 (a)처럼 커피 주문을 받고 나올 때까지 기다리는 것이 동기 방식의 예시라고 할 수 있다.
비동기(Asynchronous: 동시에 일어나지 않는)
비동기는 동시에 일어나지 않는다를 의미한다. 요청과 결과가 동시에 일어나지 않을 거라는 약속이다.
하나의 요청에 따른 응답을 즉시 처리하지 않아도, 그 대기 시간동안 또 다른 요청에 대해 처리 가능한 방식이다.
여러 개의 요청을 동시에 처리할 수 있는 장점이 있지만 동기 방식보다 속도가 떨어질 수도 있다.
위 그림의 (b)처럼 점원 한명이 커피 주문을 받고 다른 점원이 커피를 건네주는 것이 비동기 방식의 예시다.
Promise
과거, Promise가 없었던 시절에는 콜백 지옥에서 살아왔다.
function increaseAndPrint(n, callback) {
setTimeout(() => {
const increased = n + 1; //클로저
console.log(increased);
if (callback) {
callback(increased); //자기 자신 콜백함수
}
}, 1000);
}
increaseAndPrint(0, n => {
increaseAndPrint(n, n => {
increaseAndPrint(n, n => {
increaseAndPrint(n, n => {
increaseAndPrint(n, n => {
console.log('끝!');
});
});
});
});
});
Promise 를 사용하면, 비동기 작업의 개수가 많아져도 들여쓰기 코드의 깊이가 깊어지지 않게 된다. 코드를 보는데 있어서 편하게 읽을 수 있다.
Promise는 비동기 작업이 완료된 이후에 다음 작업을 연결시켜 진행할 수 있다. 작업 결과 따라 성공 또는 실패를 리턴하며 결과 값을 전달 받을 수 있다. Promise 객체를 통해 미리 함수를 등록해놓고, 결과에 따라서 resolve와 reject가 호출받으면, 함수를 실행하는 식이다. 마치 스위치를 내려 전기를 흐르게 하거나 올려서 안흐르게 하거나 같은 원리이다.
기본 문법
const myPromise = new Promise( (resolve, reject) => { //콜백함수
// 구현..
// resolve(값) or reject(값)
})
myPromise
.then(n => {}) //성공하면 콜백함수 실행. 인자 n은 resolve의 값. 리턴값은 Promise객체 (체이닝 가능)
.catch(n => {}); //실패하면 콜백함수 실행. 인자 n은 reject의 값. 리턴값은 Promise객체 (체이닝 가능)
new Promise((resolve, reject) => { //pending
//... 비동기 처리
resolve(); // fulfilled
reject(); // rejected
});
생성자를 통해 프로미스 객체를 만드는 순간 pending(대기) 상태라고 한다.
new Promise( /*executor*/ (resolve, reject) => {...} );
resolve함수를 실행하면, fulfilled(이행) 상태가 된다.
reject함수를 실행하면, rejected(거부) 상태가 된다.
const myPromise = new Promise( (resolve, reject) => {
setTimeout(function(){
let status = Math.floor(Math.random()*2);
console.log(status);
if (status){
resolve("성공"); // status is fulfilled and then 실행
}
else{
reject("실패"); // status is rejected and catch 실행
}
},1000)
});
myPromise
.then(n => console.log(`${n} 하였습니다.`))
.catch(n => console.log(`${n} 하였습니다.`));
function increaseAndPrint(n) {
return new Promise((resolve, reject)=>{
setTimeout(() => {
const increased = n + 1;
console.log(increased);
resolve(increased);
}, 1000)
})
}
increaseAndPrint(0)
.then((n) => increaseAndPrint(n))
.then((n) => increaseAndPrint(n))
.then((n) => increaseAndPrint(n))
.then((n) => increaseAndPrint(n)); // 체이닝 기법
상태 및 흐름
자세한 내용은 해당 블로그 참조
async/await
async function p2(){ // async을 지정해주면 Promise를 리턴하는 함수로 만들어준다.
return 'hello2'; //리턴값은 Promise{<resolved>: "hello2"} 자동 resolve해준다는걸 알 수있다.
// reject는 throw 에러를 날리면 된다.
}
p2().then((n) => console.log(n));
Arrow Function
const materials = [
'Hydrogen',
'Helium',
'Lithium',
'Beryllium'
];
console.log(materials.map(material => material.length));
// Expected output: Array [8, 6, 7, 9]
Scope와 Closure
Hoisting
passed by value vs passed by reference
Node.js의 특징
Database