Programming/Node.js
[Node.js] 2. JavaScript 정리
inyeong
2024. 10. 9. 14:43
2.1. ES2015+
변경 사항을 중심으로 ES2015 이상의 자바스크립트 문법에 대해 정리한다.
const / let
- var는 함수 스코프를 갖는 반면, const와 let은 블록 스코프를 갖는다.
if (true) {var x = 3;} console.log(x); // 3 if (true) {const y = 3;} console.log(y); // ReferenceError: y is not defined - const로 선언한 변수는 재할당할 수 없고, 선언할 때 반드시 값을 할당해야 한다.
const a = 0; a = 1; //TypeError: Assignment to constant variable const c; // SyntaxError: Missing initializer in const declaration
템플릿 문자열
- 백틱(`)으로 문자열을 감싸고, ${ } 안에 변수를 삽입해 문자열 안에 변수를 넣을 수 있다.
const num3 = 1; const num4 = 2; const result2 = 3; console.log(`${num3} 더하기 ${num4}는 ' ${result2}'`); // 1 더하기 2는 '3'
객체 리터럴
- 객체의 메서드에 함수를 연결할 때 콜론(:)과 function 키워드를 생략할 수 있다.
const newObject = { sayJS() {console.log('JS');} }; - 속성명과 변수명이 동일한 경우에는 한 번만 작성해도 된다.
var sayNode = function() {console.log('Node');}; const newObject = { sayNode }; newObject.sayJS(); // JS - 아래 예시의 ES6와 같은 동적 속성을 객체 리터럴 안에서 선언할 수 있다.
var es = 'ES' const newObject = { [es + 6] : 'Fantastic' }; console.log(newObject.ES6); // Fantastic
화살표 함수
- function 대신 => 기호로 함수를 선언할 수 있고, 변수에 대입하여 재사용할 수 있다.
function add1(x, y) {return x + y;} const add2 = (x, y) => {return x + y;}; - 내부에 return문 밖에 없는 경우, return 키워드와 중괄호를 생략할 수 있다.
const add3 = (x, y) => x + y; const add4 = (x, y) => (x + y); - 매개변수가 한 개이면 소괄호를 생략할 수 있다.
function not1(x) {return !x;} const not2 = x => !x ; - function 선언문과 달리, 화살표 함수는 상위 스코프의 this를 그대로 물려받는다.
var relationship1 = { name : 'zero', friends : ['nero', 'hero', 'xero'], logFriends : function() { // this는 relationship1을 가리킨다. var that = this; // this는 logFriends를 가리킨다. this.friends.forEach(function(friend){console.log(that.name, friend)}); } }; const relationship2 = { name : 'zero', friends : ['nero', 'hero', 'xero'], // 두 this 모두 relationship2를 가리킨다. logFriends() {this.friends.forEach(friend => console.log(this.name, friend))} };
구조 분해 할당
- 구조 분해 할당을 사용하면 객체와 배열로부터 속성이나 요소를 쉽게 추출할 수 있다.
- 아래 예시 코드는 candyMachine 객체의 속성을 변수에 할당한다.
const candyMachine = { status : { name : 'node', count : 5 }, getCandy() { this.status.count--; return this.status.count; } }; // candyMachine 객체의 속성을 변수와 매칭, getCandy와 count 변수 초기화 const {getCandy, status : {count}} = candyMachine; // bind를 사용해 this를 고정 const boundGetCandy = getCandy.bind(candyMachine); - 배열에 대한 구조 분해 할당도 존재한다.
const array = ['nodejs', {}, 10, true]; const [node, obj, , bool] = array;
클래스
- 클래스 문법이 추가되었지만, 자바스크립트는 여전히 프로토타입 기반으로 동작한다.
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
프로미스
① 프로미스의 개념
- 프로미스는 실행은 바로 하되 결과값은 나중에 받는 객체이다.
아래 예시에서 new Promise는 바로 실행되지만, 결과값은 then이나 catch 메서드를 통해 받는다. - new Promise로 프로미스를 생성한다.
const condition = true; // true이면 resolve, false이면 reject const promise = new Promise((resolve, reject) => { if (condition) {resolve('성공');} else {reject('실패');} }); - 프로미스 내부에서 resolve가 호출되면 then, reject가 호출되면 catch 메서드가 실행된다.
resolve와 reject의 인수는 각각 then과 catch의 매개변수로 전달된다.
promise .then((message) => { // 성공(resolve)한 경우 실행 console.log(message); // 성공 }) .catch((error) => { // 실패(reject)한 경우 실행 console.error(error); // 실패 }) .finally(() => { console.log('무조건'); // 성공, 실패에 관계 없이 실행 });
② then이나 catch에 다른 then이나 catch를 연결할 수 있다.
- 이때 이전 then의 return 값은 다음 then의 매개변수로 전달되고,
then에서 프로미스를 return하면 프로미스가 수행된 후 다음 then이나 catch가 호출된다.
promise .then((message) => { return new Promise((resolve, reject) => {resolve(message);}); }) // 처음 then에서 message를 resolve하면 다음 then에서 message2로 받을 수 있다. .then((message2) => { console.log(message2); return new Promise((resolve, reject) => {resolve(message2);}); }) // 다시 message2를 resolve한 것을 다음 then에서 message3으로 받았다. // then에서 new Promise를 return해야 다음 then에서 받을 수 있다 .then((message3) => { console.log(message3); }) .catch((error) => { console.error(error); });
③ 프로미스는 콜백이 여러 번 중첩되는 문제를 해결할 수 있다.
- 단, 메서드가 내부적으로 프로미스 객체를 가지고 있는 경우에만 사용할 수 있다.
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, Promise.reject, Promise.allSettled
- Promise.all : 프로미스 여러 개를 한 번에 실행할 수 있다. 프로미스 중 하나라도 reject 되면 catch로 넘어간다.
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.resolve : 즉시 resolve하는 프로미스를 만든다.
- Promise.reject : 즉시 reject 하는 프로미스를 만든다.
- Promise.allSettled : status를 통해 어떤 프로미스가 reject 되었는지 알 수 있다.
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); }); - reject된 Promise에 catch를 달지 않으면 에러가 발생한다.
try { Promise.reject('에러'); } catch (e) { console.error(e); // UnhandledPromiseRejection: This error originated either by throwing inside... } Promise.reject('에러').catch(() => { // catch 메서드를 붙이면 에러가 발생하지 않음 })
async / await
- async/await을 이용하면 프로미스를 사용한 코드를 더 간결하게 정리할 수 있다.
// (1) async funtion으로 함수 선언 async function findAndSaveUser(Users) { try { // (2) 프로미스 앞에 await -> 프로미스가 resolve될 때까지 기다린 후 user 변수 초기화 let user = await Users.findOne({}); user.name = 'zero'; user = await user.save(); user = await Users.findOne({gender : 'm'}); // 생략 } catch (error) { console.error(error); } } // 화살표 함수로 작성 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 await of 문을 사용해서 프로미스 배열을 순회한다.
const promise1 = Promise.resolve('성공1'); const promise2 = Promise.resolve('성공2'); (async () => { for await (promise of [promise1, promise2]) { console.log(promise); } })();
Map / Set
① Map : 객체와 유사한 자료구조로, 속성들 간의 순서를 보장한다.
- 생성
const m = new Map(); - set(키, 값) : Map에 속성을 추가한다.
m.set('a', 'b'); m.set(3, 'c'); const d = {}; m.set(d, 'e'); // 객체도 가능 - get(키) : 속성값을 조회한다.
console.log(m.get(d)); // e - size : 속성 개수를 조회한다.
console.log(m.size); // 3 - 반복문, forEach 사용 가능하다.
for (const [k, v] of m) {console.log(k, v);} m.forEach((v, k) => {console.log(k, v);}); - has(키) : 속성 존재 여부를 확인한다.
console.log(m.has(d)); - delete(키), clear() : 속성을 삭제한다.
m.delete(d); m.clear(); // 전부 제거 console.log(m.size); // 0
② Set : 배열과 유사한 자료구조로, 중복을 허용하지 않는다.
- 생성
const s = new Set(); - add() : Set에 요소를 추가한다.
s.add(1); s.add(2); s.add('1'); s.add(1); // 중복이므로 무시된다. - size : 요소 개수를 조회한다.
has() : 존재 여부를 확인한다.
console.log(s.size); // size : 3 console.log(s.has(1)); // 존재 여부 : true - 반복문, forEach 사용 가능하다.
for (const a of s) {console.log(a);} // 1 2 '1' s.forEach((a) => {console.log(a);}) // 1 2 '1' - delete(키), clear() : 속성을 삭제한다.
s.delete(2); // delete()로 요소 2 제거 s.clear(); // clear()로 모든 요소 제거 - 배열에서 중복을 허용하고 싶지 않을 때, 또는 기존 배열에서 중복을 제거하고 싶을 때 사용한다.
const arr = [1, 3, 2, 7, 2, 6, 3, 5]; const s = new Set(arr); // 배열의 중복 요소 제거 const result = Array.from(s); // set을 배열로 되돌리기 console.log(result); // 1, 3, 2, 5, 7
널 병합 / 옵셔널 체이닝
① 널 병합 (nullish coalescing) ?? 연산자
- || 연산자는 falsy 값(0, '', false, NaN, null, undefined)이면 뒤로 넘어가는 것과 달리,
- ?? 연산자는 falsy 값 중 null과 undefined를 따로 구분한다.
const a = 0; const b = a || 3; 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;
② 옵셔널 체이닝 (optional chaining) ?. 연산자
- ?. 연산자는 null이나 undefined의 속성을 조회하는 경우 에러가 발생하는 것을 막는다.
const a = {}; a.b; // a가 객체이므로 문제 없다. const c = null; try { c.d; } catch (e) { console.error(e); // TypeError: Cannot read properties of null } c?.d; // 문제 없음 try { c.f(); } catch (e) { console.error(e); // TypeError: Cannot read properties of null } c?.f(); // 문제 없음. 객체 뿐만 아니라 함수도 try { c[0]; } catch (e) { console.error(e); // TypeError: Cannot read properties of nul }
2.2. 프런트엔드 자바스크립트
AJAX (Asynchronous Javascript And XML)
- AJAX는 비동기적 웹 서비스를 개발할 때 사용하는 기법으로,
페이지 이동 없이 서버에 요청을 보내고 응답을 받을 수 있게 한다. - 보통 AJAX 요청은 jQuery나 axios 같은 라이브러리를 이용해서 보낸다.
- ex) GET 요청 보내기 : axios.get 함수의 인수로 요청을 보낼 주소를 넣는다.
<script src="https://unpkg.com/axios/dist/axios.min.js"></script> <script> (async () => { try { const result = await axios.get('https://www.zerocho.com/api/get'); console.log(result); console.log(result.data); // {} } catch (error) { console.error(error); } })(); </script> - ex) POST 요청 보내기 : axios.post 함수의 두 번째 인수로 데이터를 넣어 보낸다.
<script src="https://unpkg.com/axios/dist/axios.min.js"></script> <script> (async () => { try { const result = await axios.post('https://www.zerocho.com/api/post/json', { name: 'zerocho', birth: 1994, }); console.log(result); console.log(result.data); } catch (error) { console.error(error); } })(); </script>
FormData
- HTML form 태그의 데이터를 동적으로 제어할 수 있는 기능이다.
- FormData 생성자로 formData 객체를 만든다.
const formData = new FormData(); - FormData 메서드
append : 키-값 형식의 데이터를 저장한다.
has : 키에 해당하는 값이 있는지 알 수 있다.
formData.append('name', 'zerocho'); formData.append('item', 'orange'); formData.append('item', 'melon'); formData.has('item'); // true formData.has('money'); // false;
get : 키에 해당하는 값 하나를 가져온다.
getAll : 키에 해당하는 모든 값을 가져온다.
formData.get('item'); // orange formData.getAll('item'); // ['orange', 'melon']; formData.append('test', ['hi', 'zero']); formData.get('test'); // hi, zero
delete : 현재 키를 제거한다.
set : 현재 키를 수정한다.
formData.delete('test'); formData.get('test'); // null formData.set('item', 'apple'); formData.getAll('item'); // ['apple']; - ex) axios로 폼 데이터 서버에 보내기 - 두 번째 인수에 데이터를 넣어 보낸다.
(async () => { try { const formData = new FormData(); formData.append('name', 'zerocho'); formData.append('birth', 1994); const result = await axios.post('<https://www.zerocho.com/api/post/formdata>', formData); console.log(result); console.log(result.data); } catch (error) { console.error(error); } })();
encodeURIComponent, decodeURIComponent
- window 객체의 메서드로 주소에 한글이 들어가는 경우 사용한다.
- 한글 주소 부분만 encodeURIComponent 메서드로 감싸 안전한 문자열로 변환하고,
decodeURIComponent를 사용하여 다시 한글로 복구할 수 있다.
(async () => { try { const result = await axios.get(`https://www.zerocho.com/api/search/${encodeURIComponent('노드')}`); console.log(result); console.log(result.data); // {} } catch (error) { console.error(error); } })(); decodeURIComponent('%EB%85%B8%EB%93%9C'); // 노드
데이터 속성과 dataset
- 데이터 속성은 HTML과 관련된 데이터를 저장하는 공식적인 방법이다.
- 아래 코드에서 data-로 시작하는 HTML 태그의 속성이 데이터 속성이다.
- 데이터 속성을 사용하면 자바스크립트로 데이터에 쉽게 접근할 수 있다.
<ul> <li data-id="1" data-user-job="programmer">Zero</li> <li data-id="2" data-user-job="designer">Nero</li> <li data-id="3" data-user-job="programmer">Hero</li> <li data-id="4" data-user-job="ceo">Kero</li> </ul> <script> // li 태그의 data-id와 data-user-job에 접근 console.log(document.querySelector('li').dataset); // { id: '1', userJob: 'programmer' } </script>
Quiz
- 아래 코드에서 첫 번째 this는 함수 ( )을, 두 번째 this는 함수 ( )을 가리킨다.
const relationship = { name : 'zero', friends : ['nero', 'hero', 'xero'], logFriends() {this.friends.forEach(friend => console.log(this.name, friend))} }; - ( )을 이용하면 프로미스 여러 개를 한 번에 실행할 수 있고,
( )를 이용하면 어떤 프로미스가 reject 되었는지 알 수 있다. - async 함수 내에서 프로미스가 resolve될 때까지 기다리기 위해 사용하는 키워드는 ( ) 이다.
- 옵셔널 체이닝 연산자 (?.)는 ( ) 이나 ( ) 의 속성을 조회하는 경우 에러가 발생하는 것을 막는다.
- 널 병합 연산자(??)를 사용한 코드 1의 실행 결과는 ( ), 코드 2의 실행 결과는 ( )이다.
const c = 0; const d = c ?? 3; console.log(d); // 코드 1 const e = null; const f = e ?? 3; console.log(f); // 코드 2 - ( ) 는 비동기적 웹 서비스를 개발할 때 사용하는 기법으로,
페이지 이동 없이 서버에 요청을 보내고 응답을 받을 수 있게 한다.
Programming Quiz
- 구조 분해 할당 문법으로,
chocoMachine 객체의 getChoco와 count를 같은 이름의 변수에 할당하는 코드를 작성하시오.
const chocoMachine = { status : { name : 'node', count : 5 }, getChoco() { this.status.count--; return this.status.count; } }; - arr 배열의 중복 요소를 제거하여 변수 s에 저장하고,
s를 다시 배열로 되돌려 변수 result에 저장하는 코드를 작성하시오.
const arr = [1, 3, 2, 7, 2, 6, 3, 5]; // 배열의 중복 요소 제거 const s = ______ // set을 배열로 되돌리기 const result = _______
정답
1. relationship, relationship
2. Promise.all, Promise.allSettled
3. await
4. null, undefined
5. 0, 3
6. AJAX
1번
const {getChoco, status : {count}} = chocoMachine;
2번
const arr = [1, 3, 2, 7, 2, 6, 3, 5];
// 배열의 중복 요소 제거
const s = new Set(arr);
// set을 배열로 되돌리기
const result = Array.from(s);
출처 : 조현영, 『 Node.js 교과서 개정 3판』, 길벗(2022)
Corner Node.js 1
Editor : Snoopy