2.1 호출 스택, 이벤트 루프
· 이벤트 루프 : 이벤트 발생(setTimeout 등)시 호출할 콜백 함수들(위의 예제에서는 run)을 관리하고, 호출할 순서를 결정하는 역할
· 백그라운드 : 타이머나 I/O 작업 콜백, 이벤트 리스너들이 대기하는 공간. 여러 작업이 동시에 실행될 수 있음
· 태스크 큐 : 이벤트 발생 후 호출되어야 할 콜백 함수들이 순서대로 기다리는 공간
이벤트 루프가 호출 스택이 비워져 있을 때, 태스크 큐의 콜백을 호출 스택으로 올림
2.2 ES2015+
2.2.1 const, let
ES2015 이전에는 var로 변수를 선언
=> ES2015부터는 const와 let이 대체
var | const | let |
함수 스코프 (함수 안에서 선언시 밖에서 사용 x) |
블록 스코프 (함수 및 블록에서 선언시 밖에서 사용 x) |
|
재선언 o, 재할당 o | 재선언 x, 재할당 x | 재선언 x, 재할당 o |
if (true) {
var x = 3;
}
console.log(x); // 3
if (true) {
const y = 3;
}
console.log(y); // Uncaught ReferenceError: y is not defined
2.2.2 템플릿 문자열
문자열을 합칠 때 + 기호 때문에 지저분함
=> ES2015부터는 `(백틱) 사용 가능. 백틱 문자열 안에 ${변수}처럼 사용
var num1 = 1;
var num2 = 2;
var result = 3;
var string1 = num1 + ' 더하기 ' + num2 + '는 \'' + result + '\'';
console.log(string1); // 1 더하기 2는 '3'
// ES2015
const num3 = 1;
const num4 = 2;
const result2 = 3;
const string2 = `${num3} 더하기 ${num4}는 '${result2}'`;
console.log(string2); // 1 더하기 2는 '3'
2.2.3 객체 리터럴
훨씬 간결한 문법으로 객체 리터럴 표현 가능
· 객체의 메서드에 :function을 붙이지 않아도 됨
· { sayNode: sayNode }와 같은 것을 { sayNode }로 축약 가능
· [변수 + 값] 등으로 동적 속성명을 객체 속성 명으로 사용 가능
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
//ES2015
const newObject = {
sayJS() {
console.log('JS');
},
sayNode,
[es + 6]: 'Fantastic',
};
newObject.sayNode(); // Node
newObject.sayJS(); // JS
console.log(newObject.ES6); // Fantastic
2.2.4 화살표 함수
function add1(x, y) {
return x + y;
}
// add1을 화살표 함수로 나타낼 수 있음
const add2 = (x, y) => {
return x + y;
};
// 함수의 본문이 return만 있는 경우 return 생략
const add3 = (x, y) => x + y;
// return이 생략된 함수의 본문을 소괄호로 감싸줄 수 있음
const add4 = (x, y) => (x + y);
// not1과 not2도 같은 기능을 함 (매개변수 하나일 때 괄호 생략)
function not1(x) {
return !x;
}
const not2 = x => !x;
화살표 함수가 기존 function() {}을 대체하는 건 아님 (this가 달라짐)
· logFriends 메서드의 this 값에 주목
· forEach의 function의 this와 logFriends의 this는 다름
· that이라는 중간 변수를 이용해서 logFriends의 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();
/* 출력
zero nero
zero hero
zero xero
*/
forEach의 인자로 화살표 함수가 들어간 것에 주목
· forEach의 화살표함수의 this와 logFriends의 this가 같아짐
· 화살표 함수는 자신을 포함하는 함수의 this를 물려받음
· 물려받고 싶지 않을 때 : function() {}을 사용
// ES2015
const relationship2 = {
name: 'zero',
friends: ['nero', 'hero', 'xero'],
logFriends() {
this.friends.forEach(friend => { // 화살표 함수를 쓰면 부모의 this를 물려받음
console.log(this.name, friend); // this.friends의 this == this.name의 this
});
},
};
relationship2.logFriends();
/* 출력
zero nero
zero hero
zero xero
*/
2.2.5 구조분해 할당
const example = { a: 123, b: { c: 135, d: 146 } };
const a = example.a;
const d = example.b.d;
// 구조분해 할당
const { a, b: { d } } = example;
console.log(a); // 123
console.log(d); // 146
· const { 변수 } = 객체; 로 객체 안의 속성을 변수명으로 사용 가능
arr = [1, 2, 3, 4, 5];
const x = arr[0];
const y = arr[1];
const z = arr[4];
// 구조분해 할당
const [x, y, , , z] == arr;
· const [변수] = 배열; 각 배열 인덱스와 변수가 대응됨
var candyMachine = {
status: {
name: 'node',
count: 5,
},
getCandy: function() {
this.status.count--;
return this.status.count;
},
};
// var getCandy, var count는 candyMachine부터 시작해서 속성을 찾아 들어가야 함
var getCandy = candyMachine.getCandy;
var count = candymachine.status.count;
// 구조분해 할당 (this가 있을 때는 안 하는게 좋음)
const { getCandy, status: { count } } = candyMachine;
// count처럼 속성 안의 속성도 변수명으로 사용 가능
// 단, getCandy()를 실행했을 때 결과가 candyMachine.getCandy()와는 달라짐 (this를 사용했기 때문)
2.2.6 클래스
프로토타입 문법을 깔끔하게 작성할 수 있는 Class 문법 도입
=> Constructor(생성자), Extends(상속) 등을 깔끔하게 처리할 수 있음
=> 코드가 그룹화되어 가독성이 향상됨
// 이전
// 생성자 함수
var Human = function(type) {
this.type = type || 'human';
};
// static 메소드
Human.isHuman = function(human) {
return human instanceof Human;
}
// instance 메소드
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
// 이후
class Human {
// 생성자
constructor(type = 'human') {
this.type = type;
}
// static 메소드
static isHuman(human) {
return human instanceof Human;
}
// instance 메소드
breathe() {
alert('h-a-a-a-m');
}
}
// 상속받는 과정
class Zero extends Human {
constructor(type, firstName, lastName) {
super(type); // super로 부모의 생성자 호출
this.firstName = firstName;
this.lastName = lastName;
}
sayName() {
super.breathe(); // super로 부모의 breathe() 호출
alert(`${this.firstName} ${this.lastName}`);
}
}
const newZero = new Zero('human', 'Zero', 'Cho');
Human.isHuman(newZero); // true
2.2.7 프로미스
콜백 헬이라고 불리는 지저분한 자바 스크립트 코드의 해결책
프로미스 : 내용이 실행은 되었지만 결과를 아직 반환하지 않은 객체
· then을 붙이면 결과를 반환함
· 실행이 완료되지 않았으면 완료된 후 then 내부 함수가 실행됨
resolve(성공 리턴값) => then으로 연결
reject(실패 리턴값) => catch로 연결
finally 부분은 무조건 실행됨
const condition = true; // true면 resolve, false면 reject
const promise = new Promise((resolve, rejcet) => {
if (condition) {
resolve('성공');
} else {
reject('실패');
}
});
// 다른 코드가 들어갈 수 있음
promise
.then((message) => {
console.log(message); // 성공(resolve)한 경우 실행
})
.catch((error) => {
console.error(error); // 실패(reject)한 경우 실행
})
.finally(() => { // 끝나고 무조건 실행
console.log('무조건');
});
프로미스의 then 연달아 사용 가능 (프로미스 체이닝)
· then 안에서 return 한 값이 다음 then으로 넘어감
· return 값이 프로미스면 resolve 후 넘어감
· 에러가 난 경우 바로 catch로 이동
· 에러는 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);
});
콜백 패턴(3중첩)을 프로미스로 바꾸는 예제
// 이전
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) => { // 세 번째 콜백
// 생략
});
});
});
}
// 이후
// findOne, save 메소드가 프로미스를 지원한다고 가정
function findAndSaveUser(Users) {
Users.findOne({})
.then((user) => {
user.name = 'zero';
return user.save();
})
.then((user) => {
return Users.findOne({ gender: 'm' });
})
.catch(err => {
console.error(err);
});
}
Promise.resolve(성공 리턴값) : 바로 resolve 하는 프로미스
Promise.reject(실패 리턴값) : 바로 reject 하는 프로미스
Promise.all(배열) : 여러 개의 프로미스를 동시에 실행
· 하나라도 실패하면 catch로 감
· allSettled로 실패한 것만 추려낼 수 있음
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);
});
2.2.8 async / await
프로미스 패턴 코드를 async / await으로 한 번 더 축약 가능
· 변수 = await 프로미스; 인 경우 프로미스가 resolve된 값이 변수에 저장
· 변수 await 값; 인 경우 그 값이 변수에 저장
// 이전
function findAndSaveUser(Users) {
Users.findOne({})
.then((user) => {
user.name = 'zero';
return user.save();
})
.then((user) => {
return Users.findOne({ gender: 'm' });
})
.catch(err => {
console.error(err);
});
}
// 이후
async function findAndSaveUser(Users) {
try {
let user = await Users.findOne({});
user.name = 'zero';
user = await user.save();
user = await Users.findeOne({ gender: 'm' });
// 생략
} catch (error) {
console.error(error);
}
}
화살표 함수도 async / await 가능
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);
}
};
async 함수는 항상 promise를 반환(return)
=> then이나 await을 붙일 수 있음
async function findAndSaveUser(Users) {
// 생략
}
findAndSaveUser().then(() => { /* 생략 */ });
// 또는
async function other() {
const result = await findAndSaveUser();
}
for await (변수 of 프로미스 배열)
· 노드 10부터 지원
· resolve 된 프로미스가 변수에 담겨 나옴
· await을 사용하기 때문에 async 함수 안에서 해야 함
const promise1 = Promise.resolve('성공1');
const promise2 = Promise.resolve('성공2');
(async () => {
for await (promise of [promise1, promise2]) {
console.log(promise);
}
})();
2.2.9 Map / Set
Map은 객체와 유사한 자료구조
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); // 'a', 'b', 3, 'c', {}, 'e'
});
m.has(d); // has(키)로 속성 존재 여부 확인
console.log(m.has(d)); // true
m.delete(d); // delete(키)로 속성 삭제
m.clear(); // clear()로 전부 제거
console.log(m.size); // 0
Set은 배열과 유사한 자료구조 (기존 배열의 중복을 제거할 때도 사용)
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()로 전부 제거
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, 6, 5
2.2.10 널 병합, 옵셔널 체이닝
??(널 병합, nullish coalescing) 연산자
: || 대용으로 사용되며, 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
?.(옵셔널 체이닝, optional chaining) 연산자
: null이나 undefined의 속성을 조회하는 경우 에러가 발생하는 것을 막음
const a = {}
a.b; // a가 객체이므로 문제 없음
const c = null;
try {
c.d;
} catche (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];
} catche (e) {
console.error(e); // TypeError: Cannot read properties of null (reading '0')
}
c?.[0]; // 문제 없음
2.3 프런트엔드 자바스크립트
2.3.1 AJAX
서버로 요청을 보내는 코드
· 라이브러리 없이는 브라우저가 지원하는 XMLHttpRequest 객체 이용
· AJAX 요청 시 axios 라이브러리를 사용하는게 편함
· HTML에 아래 스크립트를 추가하면 사용할 수 있음
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
// 여기에 예제 코드
</script>
GET 요청 보내기
· axios.get 함수의 인수로 요청을 보낼 주소를 넣으면 됨
· 프로미스 기반 코드라 async / await 사용 가능
axios.get('https://www.zerocho.com/api/get')
.then((result) => {
console.log(result);
console.log(result.data); // {}
})
.catch((error) => {
console.error(error);
});
// async / await 사용
(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);
}
})();
POST 요청을 하는 코드(데이터를 담아 서버로 보내는 경우)
· 전체적인 구조는 비슷하나 두 번째 인수로 데이터를 넣어 보냄
(async () => {
try {
const result = await axios.get('https://www.zerocho.com/api/get', {
name: 'zerocho',
birth: 1994,
});
console.log(result);
console.log(result.data); // {}
} catch (error) {
console.error(error);
}
})();
2.3.2 FormData
HTML form 태그에 담긴 데이터를 AJAX 요청으로 보내고 싶은 경우 사용
const formData = new FormData();
formData.append('name', 'zerocho'); // append로 데이터를 하나씩 추가
formData.append('item', 'orange');
formData.append('item', 'melon');
formData.has('item'); // true (has로 데이터 존재 여부 확인)
formDate.has('money'); // false
formData.get('item'); // orange (get으로 데이터 조회)
formData.getAll('item'); // ['orange', 'melon'] (getAll로 데이터 모두 조회)
formData.append('test', ['hi', 'zero']);
formData.get('test'); // hi, zero
formData.delete('test'); // delete로 데이터 삭제
formData.get('test'); // null
formData.set('item', 'apple'); // set으로 데이터 수정
formData.getAll('item'); // ['apple']
FormData POST 요청으로 보내기
· axios의 data 자리에 formData를 넣어서 보내면 됨
(async () => {
try {
const formData = new FormData();
formData.append('name', 'zerocho');
formData.append('birth', 1994);
const result = await axios.get('https://www.zerocho.com/api/get', formData);
console.log(result);
console.log(result.data);
} catch (error) {
console.error(error);
}
})();
2.3.3 encodeURIComponent, decodeURIComponent
주소창에 한글 입력하면 서버가 처리하지 못하는 경우 발생
=> encodeURIComponent로 한글 감싸줘서 처리
(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);
}
})();
노드를 encodeURIComponent하면 %EB%86%B8%EB%93%9C가 됨
=> decodeURIComponent로 서버에서 한글 해석
decodeURIComponent('%EB%86%B8%EB%93%9C'); // 노드
2.3.4 data attribute와 dataset
HTML 태그에 데이터를 저장하는 방법
· 서버의 데이터를 프런트엔드로 내려줄 때 사용
· 태그 속성으로 data-속성명
· 자바스크립트에서 태그.datastet.속성명으로 접근 가능
· data-user-job => dataset.userJob
· data-id => dataset.id
· 반대로 자바스크립트 dataset에 값을 넣으면 data-속성이 생김
· dataset.monthSalary = 10000 => data-month-salary="10000"
<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>
console.log(document.querySelector('li').dataset);
// { id: '1', userJob: 'programmer' }
</script>