JavaScript는 매년 새로운 버전으로 업데이트되고 있습니다.
이 장에선, JavaScript의 새로운 문법을 간단히 알아보고, 프런트엔드에서 사용하는 자바스크립트 코드를 알아봅니다.
1. ES2015(ES6)
2015년 JavaScript 문법에 큰 변화가 있었습니다.
JavaScript는 현재 ES2020까지 발표되었고, 일부 구형 브라우저에선 최신 JavaScript 문법을 사용할 수 없습니다.
이로 인해, 호환성에 문제가 있었지만 babel과 같은 구형 브라우저에 맞게 문법을 변환해주는 도구가 있어서 큰 문제는 되지 않습니다.
ES6 부터는, 다른 언어들의 장점을 본딴 편리한 기능이 많이 추가되었기 때문에, 이제는 ES6 문법을 배워야 할 때입니다.
2. const, let
이전에는 변수를 선언할 때 var 키워드를 사용했지만, 이제는 const와 let으로 대체합니다.
아래 예제들로 const와 let이 가지는 특징인 블록 스코프에 대해 알아봅니다.
if ( true ) {
var x = 3;
}
console.log(x);
if ( true ) {
const y = 3;
}
console.log(y);
결과는 아래와 같습니다.
var을 사용한 x는 정상적으로 출력되지만, const를 사용한 y는 에러가 발생합니다.
1) var은 함수 스코프를 가지므로, if 문의 블록과 상관없이 접근할 수 있습니다.
2) const는 블록 스코프를 가지므로, if 문의 블록 밖에서는 접근할 수 없습니다.
블록이란, 중괄호 "{ }"의 범위를 뜻합니다.
함수 스코프 대신 블록 스코프를 사용함으로써, 호이스팅 문제도 해결되고 코드 관리도 수월해졌습니다.
여기서 호이스팅이란, JavaScript코드를 실행하기 전, 선언된 변수나 함수들을 맨 위로 올리는 JavaScript의 기본적인 동작을 뜻합니다.
https://www.w3schools.com/js/js_hoisting.asp
const와 let의 차이점은 변수의 값을 변경할 수 있는지의 차이입니다.
예를 들어, const는 변수를 선언하고 초기값을 할당하지 않거나 변경하려 하면 에러가 발생합니다.
실제로 JavaScript 코드를 작성할 땐 변수의 값을 변경하는 경우가 적으므로, 기본적으로 const를 사용하고, 다른 값을 할당해야 할 때 let을 사용하는 것이 좋습니다.
3. 템플릿 문자열
ES6+에선 스트링을 선언할 때 사용하던 큰 따옴표(")와 작은 따옴표(') 외에 백틱(`)이 새로 추가되었습니다.
백틱을 사용하면 문자열 안에 변수를 넣을 수 있습니다.
const num1 = 1;
const num2 = 2;
const result = 3;
const string = `${num1} 더하기 ${num2}는 '${result}'`;
console.log( string );
'${result}'처럼 작은 따옴표를 string으로 넣을 수도 있습니다.
4. 객체 리터럴
리터럴(Literal)이란, JavaScript 데이터의 그 자체값입니다.
리터럴의 종류는 아래와 같습니다.
1) { } (Object)
2) [ ] (Array)
3) function( ){ } (function)
4) '123' (String)
5) 123 (Number)
6) true (Boolean)
7) undefined (undefined)
ES6+에선 객체 리터럴( { } )에 편리한 기능이 추가되었습니다.
아래 예제는 ES5 JavaScript 코드입니다.
var sayNode = function() {
console.log('Node');
}
var es = 'ES';
var oldObject = {
sayJS: function() {
console.log('JS');
},
sayNode: sayNode
};
oldObject[ es + 6 ] = 'Fantastic';
oldObject.sayNode();
oldObject.sayJS();
console.log(oldObject.ES6);
위 코드를 ES6+ 문법으로 변경하면, 아래와 같이 작성할 수 있습니다.
const newObject = {
sayJS() {
console.log('JS');
},
sayNode,
[es + 6]: 'Fantastic'
};
newObject.sayNode();
newObject.sayJS();
console.log(oldObject.ES6);
위와 같이, ES6+에선 객체 리터럴을 만들 때 콜론(:)과 function을 붙이지 않아도 되고, [es + 6]처럼 동적인 속성명을 사용할 수도 있습니다.
또한, 속성명과 변수명이 같을 경우, 한 번만 써도 되게 바뀌었습니다.
{ name: name, age: age } // ES5
{ name, age } // ES6+
5. 화살표 함수
ES6+에선 화살표 함수가 새로 추가되었습니다.
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~4, not1~2는 모두 같은 기능을 하는 함수입니다. 이런 식으로 함수를 간결하게 선언해 사용할 수 있습니다.
기존 함수와 다른 점은 this 바인드 방식입니다.
var relationship1 = {
name: 'jaynull',
friends: ['a', 'b', 'c'],
logFriends: function() {
var that = this; // relationship1을 가리키는 this를 that에 저장
this.friends.forEach(function (friend) {
console.log(that.name, friend);
});
},
};
relationship1.logFriends();
const relationship2 = {
name: 'jaynull',
friends: ['a', 'b', 'c'],
logFriends() {
this.friends.forEach(friend => {
console.log(this.name, friend);
});
},
};
relationship2.logFriends();
relationship1에선 this를 that이라는 변수를 통해 간접적으로 접근했지만,
relationship2에선 forEach 함수에 화살표 함수를 사용해 logFriends()의 this를 그대로 사용할 수 있습니다. 상위 스코프의 this를 물려받는 것입니다.
6. 구조분해 할당
구조 분해 할당 구문은 배열이나 객체의 속성을 해체하여 그 값을 개별 변수에 담을 수 있게 하는 JavaScript 표현식입니다.
아래는 기존 ES5 문법의 예제입니다.
// ES5
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;
위 코드를 ES6+ 문법을 적용하면 아래와 같습니다.
// ES6+
const candyMachine = {
status: {
name: 'node',
count: 5,
},
getCandy() {
this.status.count--;
return this.status.count;
}
};
const { getCandy, status: { count } } = candyMachine;
위처럼, candyMachine 객체 안의 속성을 찾아서 getCandy, count 변수를 매칭해 초기화할 수 있습니다.
배열에도 구조분해 할당을 적용할 수 있습니다.
// ES5
var arr = ['nodejs', {}, 10, true];
var node = arr[0];
var obj = arr[1];
var bool = arr[3];
// ES6
const ARR = ['nodejs', {}, 10, true];
const [NODE, OBJ, , BOOL] = ARR;
구조분해 할당 문법은 코드 줄 수를 줄여주므로 유용하게 사용할 수 있습니다.
7. 클래스
JavaScript는 여전히 프로토타입 기반으로 동작하지만, 다른 객체지향 언어들처럼 클래스 개념이 새로 추가되었습니다.
다음은 기존의 프로토타입 상속 예제입니다.
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 Jay = function(type, firstName, lastName) {
Human.apply(this, arguments);
this.firstName = firstName;
this.lastName = lastName;
};
Jay.prototype = Object.create(Human.prototype);
Jay.prototype.constructor = Jay; // 상속받는 부분
Jay.prototype.sayName = function() {
alert(this.firstName + ' ' + this.lastName);
};
var oldJay = new Jay('human', 'Jay', 'Null');
Human.isHuman(oldJay);
다음은 위 예제를 클래스로 변경한 코드입니다.
class Human {
constructor(type = 'human') {
this.type = type;
}
static isHuman(human) {
return human instanceof Human;
}
breathe() {
alert('h-a-a-a-m');
}
};
class Jay extends Human {
constructor(type, firstName, lastName) {
super(type);
this.firstName = firstName;
this.lastName = lastName;
}
sayName() {
super.breathe();
alert(`${this.firstName} ${this.lastName}`);
}
}
const newJay = new Jay('Human', 'Jay', 'Null');
Human.isHuman(newJay);
위처럼 클래스를 사용해 프로토타입 방식보다 상속을 좀 더 쉽고, 좀 더 가독성 있게 코드를 작성할 수 있습니다.
8. 프로미스
JavaScript와 노드에서는 주로 비동기를 사용하고, 이벤트 리스너를 사용할 때 콜백 함수를 자주 사용합니다.
ES6+에서는 콜백 대신 프로미스를 기반으로 재구성되며, 콜백 지옥 현상을 극복할 수 있는 객체입니다.
아래는 프로미스 객체를 생성하는 예제입니다.
const condition = true;
const promise = new Promise((resolve, reject) => {
if ( condition ) {
resolve('성공');
} else {
reject('실패');
}
});
promise
.then((message) => {
console.log(message);
})
.catch((error) => {
console.error(error);
})
.finally(() => {
console.log('무조건');
});
프로미스는 resolve와 reject를 매개변수로 하는 new Promise로 생성할 수 있으며, 프로미스 내부에서 resolve가 호출되면 .then()이 실행되고, reject가 호출되면 .catch()가 실행되며, 마지막으로 .finally()가 무조건 실행됩니다.
.then(), .catch() 다음에 또 다른 .then(), .catch()가 올 수도 있으며, 이전 .then()의 return 값이 다음 .then()의 매개변수로 전달됩니다.
promise
.then((message) => {
return new Promise((resolve, reject) => {
resolve(message);
});
})
.then((message2) => {
console.log(message2);
return message2
})
.then((message3) => {
console.log(message3);
})
.catch((error) => {
console.error(error);
});
프로미스 여러 개를 한 번에 실행하는 방법이 있습니다. 여러 중첩 콜백을 간결하게 바꿀 수 있습니다.
const promise1 = Promise.resolve('성공1');
const promise2 = Promise.resolve('성공2');
Promise.all([promise1, promise2])
.then((result) => {
console.log(result);
})
.catch((error) => {
console.error(rror);
});
Promise.all에 프로미스들을 넣으면 모두 resolve될 때까지 기다렸다가 .then()으로 넘어갑니다.
result 매개변수엔 각 프로미스의 결괏값이 배열로 들어 있습니다.
이 중 하나라도 reject되면 .catch()로 넘어갑니다.
9. async/await
노드 7.6+ 버전에서 지원되고, ES2017에서 추가된 편리한 기능입니다.
프로미스가 콜백 지옥을 해결했지만, async/await 문법은 코드를 더 깔끔하게 만들게 해줍니다.
아래는 프로미스의 .then(), .catch() 함수 대신, async/await을 사용한 예제입니다.
async function findAndSaveUser(Users) {
let user = await Users.findOne({});
user.name = 'jaynull';
user = await user.save();
user = await Users.findOne({ gender: 'm' });
}
async를 사용한 함수는 await의 결과가 resolve 될 때까지 기다린 다음, 다음 작업을 수행합니다.
위 코드는 에러 처리를 할 수 없기 때문에, try/catch 문으로 감싸서 해결할 수 있습니다.
async function findAndSaveUser(Users) {
try {
let user = await Users.findOne({});
user.name = 'jaynull';
user = await user.save();
user = await Users.findOne({ gender: 'm' });
} catch(error) {
console.error(error);
}
}
화살표 함수도 async와 함께 사용할 수 있습니다.
const findAndSaveUser = async (Users) => {
try {
let user = await Users.findOne({});
user.name = 'jaynull';
user = await user.save();
user = await Users.findOne({ gender: 'm' });
} catch(error) {
console.error(error);
}
}
ES2018+에선 for 문과 함께 사용할 수도 있습니다.
const promise1 = Promise.resolve('성공1');
const promise2 = Promise.resolve('성공2');
(async () => {
for await ( promise of [promise1, promise2]) {
console.log(promise);
}
})();
async 함수의 반환값을 항상 Promise로 감싸지기 때문에, .then(), .catch()를 붙이거나, 또 다른 async 함수 안에서 await을 붙여서 처리할 수 있습니다.
'JavaScript > NodeJS' 카테고리의 다른 글
Mac) node.js 버전 변경 (1) | 2023.07.09 |
---|---|
Express CORS 설정 (1) | 2023.01.18 |
Rocky Linux에 node.js 설치하기 (0) | 2021.11.16 |
"Node.js 교과서" 정리 노트 - 1장, 노드 시작하기 (0) | 2021.10.10 |