2. ES2015+
ES2015란 ECMA라는 국제 기구에서 만든 표준문서인 ES의 6번째 개정판 문서에 있는 표준 스펙을 말한다.
6번째 버전이 2015년에 나왔기 때문에 ES2015이고 ES6라고도 한다.
2015을 기점으로 매년 문법 변경 사항이 발표되고 있으며 새로운 문법 상세에 대해 활발한 논의가 이뤄지고 있다.
2023년을 기준으로 ES2023까지 나왔다.
2.1 const, let
var 대신 이제 const와 let이 대체한다.
if (true) {
var x = 3;
}
console.log(x); // 3
if (true) {
const y = 3;
}
console.log(y); // Uncaught ReferenceError: y is not defined
x는 정상적으로 출력되지만 y는 에러가 발생한다.
var은 함수 스코프를 가지므로 if문의 블록과 관계없이 접근할 수 있다.
하지만 const와 let은 블록 스코프를 가지므로 블록 밖에서는 변수에 접근할 수 없다.
함수 스코프 대신 블록 스코프를 사용하면 호이스팅 같은 문제도 해결되고 코드 관리도 수월해진다.
const a = 0;
a = 1; // Uncaught TypeError: Assignment to constant variable
let b = 0;
b = 1; // 1
const c; // Uncaught SyntaxError: Missing initializer in const declaration
const와 let의 차이점은 무엇일까?
const는 한 번 값을 할당하면 다른 값을 할당할 수 없다.
따라서 const로 선언한 변수를 상수라고 한다.
2.2 템플릿 문자열
ES2015 문법에 백틱(`)이라는 새로운 문자열이 생겼다.
특징은 문자열 안에 변수를 넣을 수 있다는 것이다.
var num1 = 1;
var num2 = 2;
var result = 3;
var string1 = num1 + ' 더하기 ' + num2 + '는 \'' + result + '\'';
console.log(string1); // 1 더하기 2는 '3'
위의 코드는 띄어쓰기, 변수, 연산자를 사용하기 때문에 가독성이 좋지 않다.
하지만 백틱을 사용하면 가독성을 높일 수 있다.
const num3 = 1;
const num4 = 2;
const result2 = 3;
const string2 = `${num3} 더하기 ${num4}는 ' ${result2}'`;
console.log(string2); // 1 더하기 2는 '3'
2.3 객체 리터럴
객체 리터럴에 편리한 기능들이 추가되었다.
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
위 코드를 아래와 같이 작성할 수 있다.
const newObject = {
sayJS() {
console.log('JS');
},
sayNode,
[es + 6]: 'Fantastic',
};
newObject.sayNode(); // Node
newObject.sayJS(); // JS
console.log(newObject.ES6); // Fantastic
sayNode: sayNode 처럼 속성명과 변수명이 동일한 경우 한 번만 써도 되게 바뀌었다.
ES2015 문법에서는 객체 리터럴 안의 동적 속성을 선언해도 된다.
2.4 화살표 함수
화살표 함수라는 새로운 함수가 추가되었다.
화살표 함수에서는 function 대신 => 기호로 함수를 선언하며 변수에 대입하면 나중에 재사용이 가능하다.
function add1(x, y) {
return x + y;
}
const add2 = (x, y) => {
return x + y;
};
const add3 = (x, y) => x + y;
const add4 = (x, y) => (x + y);
function not1(x) {
return !x;
}
const not2 = x => !x;
add1, add2, add3, add4는 같은 기능을 하는 함수이고, not1, not2 함수도 마찬가지이다.
add3와 add4 처럼 return문을 줄일 수 있고, not2처럼 매개변수가 한 개이면 소괄호를 생략할 수 있다.
기존 function과 다른 점은 this 바인드 방식이다.
var relationship1 = {
name: 'zero',
friends: ['nero', 'hero', 'xero'],
logFriends: function () {
var that = this; // relationship1을 가리키는 this를 that에 저장
this.friends.forEach(function (friend) {
console.log(that.name, friend);
});
},
};
relationship1.logFriends();
const relationship2 = {
name: 'zero',
friends: ['nero', 'hero', 'xero'],
logFriends() {
this.friends.forEach(friend => {
console.log(this.name, friend);
});
},
};
relationship2.logFriends();
relationship1의 경우 forEach문에서 function 선언문을 사용하여 각자 다른 함수 스코프의 this를 가지므로 that이라는 변수를 사용해서 relationship1에 접근하고 있다.
relationship2의 경우 forEach문에서 화살표 함수를 사용하여 바깥 스코프인 logFriends()의 this를 그대로 사용할 수 있다.
2.5 구조 분해 할당
구조 분해 할당을 사용하면 객체와 배열로부터 속성이나 요소를 쉽게 꺼낼 수 있다.
var candyMachine = {
status: {
name: 'node',
count: 5,
},
getCandy: function () {
this.status.count--;
return this.status.count;
},
};
var getCandy = candyMachine.getCandy;
var count = candyMachine.status.count;
위 코드를 아래와 같이 바꿀 수 있다.
const candyMachine = {
status: {
name: 'node',
count: 5,
},
getCandy() {
this.status.count--;
return this.status.count;
},
};
const { getCandy, status: { count } } = candyMachine;
candMachine 객체 안의 속성을 찾아 변수와 매칭한다.
여러 단계의 속성도 찾을 수 있다.
다만 구조 분해 할당을 사용하면 함수의 this가 달라질 수 있다.
달라진 this를 원래대로 바꿔주려면 bind 함수를 따로 사용해야 한다.
배열에 대한 구조 분해 할당 문법도 존재한다.
var array = [‘nodejs’, {}, 10, true];
var node = array[0];
var obj = array[1];
var bool = array[3];
위 코드를 아래와 같이 바꿀 수 있다.
const array = [‘nodejs’, {}, 10, true];
const [node, obj, , bool] = array;
2.6 클래스
다른 언어처럼 클래스 기반으로 동작하는 것이 아닌 프로토타입 기반으로 동작한다.
var Human = function(type) {
this.type = type || 'human';
};
Human.isHuman = function(human) {
return human instanceof Human;
}
Human.prototype.breathe = function() {
alert('h-a-a-a-m');
};
var Zero = function(type, firstName, lastName) {
Human.apply(this, arguments);
this.firstName = firstName;
this.lastName = lastName;
};
Zero.prototype = Object.create(Human.prototype);
Zero.prototype.constructor = Zero; // 상속하는 부분
Zero.prototype.sayName = function() {
alert(this.firstName + ' ' + this.lastName);
};
var oldZero = new Zero('human', 'Zero', 'Cho');
Human.isHuman(oldZero); // true
Human 생성자 함수가 있고, 그 함수를 Zero 생성자 함수가 상속한다.
Zero 생성자 함수를 보면 상속받기 위한 코드가 상당히 난해함을 알 수 있다.
아래 코드는 위 코드를 클래스 기반으로 바꾼 코드이다.
class Human {
constructor(type = 'human') {
this.type = type;
}
static isHuman(human) {
return human instanceof Human;
}
breathe() {
alert('h-a-a-a-m');
}
}
class Zero extends Human {
constructor(type, firstName, lastName) {
super(type);
this.firstName = firstName;
this.lastName = lastName;
}
sayName() {
super.breathe();
alert(`${this.firstName} ${this.lastName}`);
}
}
const newZero = new Zero('human', 'Zero', 'Cho');
Human.isHuman(newZero); // true
생성자 함수는 constructor 안으로 들어갔고, isHuman 같은 클래스 함수는 static 키워드로 전환되었다.
상속도 간단해져 extends 키워드로 쉽게 상속할 수 있다.
2.7 프로미스
자바스크립트와 노드에서는 주로 비동기를 접한다.
ES2015부터는 콜백 대신 프로미스 기반으로 재구성되며, 악명 높은 콜백 지옥 현상을 극복했다는 평가를 받고 있다.
const condition = true; // true이면 resolve, false이면 reject
const promise = new Promise((resolve, reject) => {
if (condition) {
resolve('성공');
} else {
reject('실패');
}
});
// 다른 코드가 들어갈 수 있음
promise
.then((message) => {
console.log(message); // 성공(resolve)한 경우 실행
})
.catch((error) => {
console.error(error); // 실패(reject)한 경우 실행
})
.finally(() => { // 끝나고 무조건 실행
console.log('무조건');
});
new Promise로 프로미스를 생성할 수 있으며 안에 resolve와 reject를 매개변수로 받는 콜백 함수를 넣는다.
이렇게 만든 promise 변수에 then과 catch 메서드를 붙일 수 있다.
프로미스를 쉽게 설명하면 실행은 바로 하되 결괏값은 나중에 받는 객체이다.
promise
.then((message) => {
return new Promise((resolve, reject) => {
resolve(message);
});
})
.then((message2) => {
console.log(message2);
return new Promise((resolve, reject) => {
resolve(message2);
});
})
.then((message3) => {
console.log(message3);
})
.catch((error) => {
console.error(error);
});
처음 then에서 message를 resolve하면 다음 then에서 message2를 받을 수 있다.
다시 message2를 resolve하면 다음 then에서 message3를 받는다.
단, then에서 new Promise를 return해야 다음 then에서 받을 수 있다.
아래 코드는 콜백 함수가 세번 중첩되어 있다.
function findAndSaveUser(Users) {
Users.findOne({}, (err, user) => { // 첫 번째 콜백
if (err) {
return console.error(err);
}
user.name = 'zero';
user.save((err) => { // 두 번째 콜백
if (err) {
return console.error(err);
}
Users.findOne({ gender: 'm' }, (err, user) => { // 세 번째 콜백
// 생략
});
});
});
}
위 코드는 아래 코드처럼 바꿀 수 있다.
function findAndSaveUser(Users) {
Users.findOne({})
.then((user) => {
user.name = 'zero';
return user.save();
})
.then((user) => {
return Users.findOne({ gender: 'm' });
})
.then((user) => {
// 생략
})
.catch(err => {
console.error(err);
});
}
콜백 함수를 사용한 것과 다르게 코드의 깊이가 깊어지지 않는다.
하지만 모든 콜백 함수를 바꿀 수 있는 것은 아니다.
메서드가 프로미스 방식을 지원해야 한다.
Promise.all을 활용하면 프로미스 여러 개를 한 번에 실행할 수 있다.
Promise.resolve는 즉시 resolve하는 프로미스를 만드는 방법이다.
즉시 reject하는 Promise.reject도 있다.
const promise1 = Promise.resolve('성공1');
const promise2 = Promise.resolve('성공2');
Promise.all([promise1, promise2])
.then((result) => {
console.log(result); // ['성공1', '성공2'];
})
.catch((error) => {
console.error(error);
});
Promise 중 하나라도 reject되면 catch로 넘어가기 때문에 여러 프로미스 중 어떤 프로미스가 reject 되는지 알 수 없다.
정확히 어떤 프로미스에서 reject 되었는지 알기 위해서는 Promise.all 대신 Promise.allSettled를 사용해야 한다.
const promise1 = Promise.resolve('성공1');
const promise2 = Promise.reject('실패2');
const promise3 = Promise.resolve('성공3');
Promise.allSettled([promise1, promise2, promise3])
.then((result) => {
console.log(result);
/* [
* { status: 'fulfilled', value: '성공1' },
* { status: 'rejected', reason: '실패2' },
* { status: 'fulfilled', value: '성공3' }
* ]
*/
})
.catch((error) => {
console.error(error);
});
Promise.allSettled를 사용하면 결괏값이 좀 더 자세해 어떤 프로미스가 reject되었는지 status를 통해 알 수 있다.
Node 16 버전부터는 reject된 Promise에 catch를 달지 않으면 UnhandledPromiseRejection 에러가 발생한다.
try {
Promise.reject('에러');
} catch (e) {
console.error(e); // UnhandledPromiseRejection: This error originated either by throwing inside...
}
Promise.reject('에러').catch(() => {
// catch 메서드를 붙이면 에러가 발생하지 않음
})
2.8 async/await
노드 7.6버전부터 지원되는 기능으로, ES2017에서 추가되었다.
프로미스가 콜백 지옥을 해결했지만 then과 catch가 계속 반복된다.
async/await 문법은 프로미스를 한 번 더 깔끔하게 줄인다.
function findAndSaveUser(Users) {
Users.findOne({})
.then((user) => {
user.name = 'zero';
return user.save();
})
.then((user) => {
return Users.findOne({ gender: 'm' });
})
.then((user) => {
// 생략
})
.catch(err => {
console.error(err);
});
}
위 코드는 프로미스 코드이다.
콜백과 다르게 코드가 깊어지진 않지만 코드의 길이는 길다.
async function findAndSaveUser(Users) {
let user = await Users.findOne({});
user.name = 'zero';
user = await user.save();
user = await Users.findOne({ gender: 'm' });
// 생략
}
함수의 선언부에 async를 추가하고 프로미스 앞에 await를 붙인다.
예를 들어 User.findOne()이 resolve될 때까지 기다린 다음에 user 변수를 초기화 하는 것이다.
위 코드는 에러를 처리하는 부분이 없으므로 아래 코드와 같은 작업이 필요하다.
async function findAndSaveUser(Users) {
try {
let user = await Users.findOne({});
user.name = 'zero';
user = await user.save();
user = await Users.findOne({ gender: 'm' });
// 생략
} catch (error) {
console.error(error);
}
}
프로미스의 catch 메소드처럼 try/catch문의 catch가 에러를 처리한다.
화살표 함수도 async와 같이 사용 가능하다.
const findAndSaveUser = async (Users) => {
try {
let user = await Users.findOne({});
user.name = 'zero';
user = await user.save();
user = await Users.findOne({ gender: 'm' });
// 생략
} catch (error) {
console.error(error);
}
};
for문과 async/await을 같이 써서 프로미스를 순차적으로 실행할 수 있다.
for문과 함께 쓰는 것은 노드10 버전부터 지원하는 ES2018 문법이다.
const promise1 = Promise.resolve('성공1');
const promise2 = Promise.resolve('성공2');
(async () => {
for await (promise of [promise1, promise2]) {
console.log(promise);
}
})();
2.9 Map/Set
ES2015에는 새로운 자료구조들이 추가되었따. 그중 자주 쓰이는 것은 Map과 Set이다.
Map은 객체와 유사하고, Set은 배열과 유사하다.
const m = new Map();
m.set('a', 'b'); // set(키, 값)으로 Map에 속성 추가
m.set(3, 'c'); // 문자열이 아닌 값을 키로 사용 가능합니다
const d = {};
m.set(d, 'e'); // 객체도 됩니다
m.get(d); // get(키)로 속성값 조회
console.log(m.get(d)); // e
m.size; // size로 속성 개수 조회
console.log(m.size) // 3
for (const [k, v] of m) { // 반복문에 바로 넣어 사용 가능합니다
console.log(k, v); // 'a', 'b', 3, 'c', {}, 'e'
} // 속성 간의 순서도 보장됩니다
m.forEach((v, k) => { // forEach도 사용 가능합니다
console.log(k, v); // 결과는 위와 동일
});
m.has(d); // has(키)로 속성 존재 여부를 확인합니다
console.log(m.has(d)); // true
m.delete(d); // delete(키)로 속성을 삭제합니다
m.clear(); // clear()로 전부 제거합니다
console.log(m.size); // 0
Map은 속성들 간의 순서를 보장하고 반복문을 사용할 수 있다.
속성명으로 문자열이 아닌 값도 사용 가능하고, size 메소드를 통해 속성 수를 쉽게 알 수 있다는 점에서 일반 객체와 다르다.
const s = new Set();
s.add(false); // add(요소)로 Set에 추가합니다
s.add(1);
s.add('1');
s.add(1); // 중복이므로 무시됩니다
s.add(2);
console.log(s.size); // 중복이 제거되어 4
s.has(1); // has(요소)로 요소 존재 여부를 확인합니다
console.log(s.has(1)); // true
for (const a of s) {
console.log(a); // false 1 '1' 2
}
s.forEach((a) => {
console.log(a); // false 1 '1' 2
})
s.delete(2); // delete(요소)로 요소를 제거합니다
s.clear(); // clear()로 전부 제거합니다
Set은 중복을 허용하지 않는다는 것이 가장 큰 특징이다.
따라서 배열 자료구조를 사용하고 싶으나 중복은 허용하고 싶지 않을 때 사용하면 된다.
기존 배열에서 중복 제거도 가능하다.
const arr = [1, 3, 2, 7, 2, 6, 3, 5];
const s = new Set(arr);
const result = Array.from(s);
console.log(result); // 1, 3, 2, 7, , 5
2.10 널 병합/옵셔널 체이닝
ES2020에서 추가된 ??(널 병합) 연산자와 ?. (옵셔널 체이닝) 연산자이다.
널 병합 연산자는 주로 ||연산자 대용으로 사용되며, falsy 값 중 null과 undefined만 따로 구분한다.
const a = 0;
const b = a || 3; // || 연산자는 falsy 값이면 뒤로 넘어감
console.log(b); // 3
const c = 0;
const d = c ?? 3; // ?? 연산자는 null과 undefined일 때만 뒤로 넘어감
console.log(d); // 0;
const e = null;
const f = e ?? 3;
console.log(f); // 3;
const g = undefined;
const h = g ?? 3;
console.log(h); // 3;
옵셔널 체이닝 연산자는 null이나 undefined의 속성을 조화하는 경우 에러가 발생하는 것을 막는다.
const a = {}
a.b; // a가 객체이므로 문제없음
const c = null;
try {
c.d;
} catch (e) {
console.error(e); // TypeError: Cannot read properties of null (reading 'd')
}
c?.d; // 문제없음
try {
c.f();
} catch (e) {
console.error(e); // TypeError: Cannot read properties of null (reading 'f')
}
c?.f(); // 문제없음
try {
c[0];
} catch (e) {
console.error(e); // TypeError: Cannot read properties of null (reading '0')
}
c?.[0]; // 문제없음
'Programming > NodeJS' 카테고리의 다른 글
Node.js 교과서 [파일 시스템] #3.3 (0) | 2024.02.04 |
---|---|
Node.js 교과서 [노드 내장 모듈] #3.2 (0) | 2024.02.04 |
Node.js 교과서 [REPL, 모듈, 노드 내장 객체] #3 (1) | 2024.02.04 |
Node.js 교과서 [Front-end] #2.2 (0) | 2024.02.02 |
Node.js 교과서 #1 (0) | 2024.01.13 |