Truthy와 Falsy
Thuthy & Falsy란?
참이나 거짓을 의미하지 않는 값도, 조건문 내에서 참이나 거짓으로 평가하는 특
Thuthy한 값 : 참 같은 값
Falsy 한 값 : 거짓 같은 값
이를 이요하면 조건문을 간결하게 만들 수 있다.
- JavaScript에서는 참, 거짓이 아닌 값도 참, 거짓으로 평가한다.
- JavaScript의 모든 값은 Truthy하거나 Falsy 하다.
// 1. Falsy한 값
let f1 = undefined;
let f2 = null;
let f3 = 0;
let f4 = -0;
let f5 = NaN;
let f6 = "";
let f7 = 0n;
if (!f1) {
// ture가 됨..
console.log("falsy");
}
// 2. truthy한 값
// -> 7가지 Falsy한 값을 제외한 나머지 모든 값
let t1 = "hello";
let t2 = 123;
let t3 = [];
let t4 = {};
let t5 = () => {};
if (t4) {
// true
console.log("Truthy");
}
// 3.활용 사례
// 활용 전전
function printName(person) {
if (person === undefined || person === null) {
console.log("person의 값이 없음");
}
console.log(person.name);
}
let person = { name: "ㅇㅇㅇ" };
printName(person);
// 활용 후(조건문 간결해짐..)
function printName2(person2) {
// undefind나 null이 들어오면
if (!person2) {
// !falthy가 되기 때문에 true가 됨..
console.log("person의 값이 없음");
}
console.log(person.name);
}
let person2 = null;
단락 평가(Short-circuit Ecaluation)
논리 연산식(AND, OR ...) 에서 첫 번째 피연산자 값만으로 이 연산의 결과를 확정할 수 있다면, 두번 째 피연산자에는 접근조차 하지 않는 실행방식.
💡truthy한 값 연산 결과
* AND
truthy한 값 || truthy한 값
단락 평가로 인해 앞의 값이 나옴
* OR
truthy한 값 && truthy한 값
결과 값은 뒤에 값이 나옴
function retrunFalse() {
console.log("False 함수");
return false;
}
function retrunTrue() {
console.log("True 함수");
return true;
}
// # 예시 1
// 첫 번째 피연산자가 이미 flase이기 때문에, 두번째 피연산자에는 접근조차 하지 않는다.
// retrunTrue()함수를 호출조차 되지 않음..
console.log(retrunFalse() && retrunTrue());
// 단락 평가 O
// >> False 함수, false
// true값은 안나옴
console.log(retrunTrue() && retrunFalse());
// 단락 평가 X
// >> True 함수 true False 함수 false -> 이렇게 다 나옴옴
console.log(retrunTrue() || retrunFalse());
// 단락 평가 O
// >> True 함수 true
// # 예시 2
// falsy한 값, truthy한 값에도 적용된다.
function retrunFalthy() {
console.log("Falthy 함수");
return undefined;
}
function retrunTruthy() {
console.log("Truthy 함수");
return undefined;
}
console.log(retrunTruthy() || retrunFalthy());
// 단락 평가 O
// retrunTruthy만 나오고 호출 끝.
// # 단락 평가 활용 사례
// 활용 전
function printName1(person) {
if (!person) {
console.log("person에 값이 없음");
return;
}
console.log(person.name);
}
// 활용 후 1: 활용 전의 if 조건문 없어도 된다!
function printName2(person) {
console.log(person && person.name);
}
printName2(); // >> undefined
printName2({ name: "ㅇㅇㅇ" }); // >> ㅇㅇㅇ
// 활용 후 2
function printName3(person) {
const name = person && person.name;
console.log(name || "person의 값이 없음");
}
printName3(); // >> "person의 값이 없음"
// 1) undifined는 falthy한 값
// 2) const name = undifined가 됨..(이미 false이기 때문에 뒤에 값은 의미 없음..어차피 false 단락 평가)
// 3) consol.log(undifined || "person의 값이 없음" - true) -> 이렇게 되기 때문에 "person의 값이 없음" 이게 나옴..
printName3({ name: "ㅇㅇㅇ" }); // >> ㅇㅇㅇ
// 1) const name = person - truthy한 값 && person.name -> truthy한 값으로 person.name이 나옴..
// 2) console.log(name -> truthy한 값 || "person의 값이 없음" - truthy한 값)
// -> 단락 평가로 인해 앞에 값이 나옴(ㅇㅇㅇ)
구조 분해 할당
// 1. 배열의 구조 분해 할당
let arr = [1, 2, 3];
let [one, two, three] = arr;
console.log(one, two, three);
// >> 1, 2, 3
let [one2, two2, three2] = arr;
console.log(one2, two2);
// >> 1, 2
let [one3, two3, three3, four3] = arr;
console.log(one3, two3, three3, four3); // 존재하지 않는 것을 받으면 undefined가 뜸..
// >> 1, 2, 3, undefined
let [one4, two4, three4, four4 = 4] = arr;
console.log(one4, two4, three4, four4);
// 2. 객체의 구조 분해 할당
let person = {
name: 'ooo',
age: 27,
hobby: '테니스',
};
// let { name, age, hobby, extra } = person;
console.log(name, age, hobby, extra);
let {
age: myAge, // age를 myAge로 받고 싶을 때
hobby,
name,
extra = 'hello',
} = person;
console.log(name, myAge, hobby, extra);
// 3. 객체 구조 분해 할당을 이용해서 함수의 매개변수를 받는 방법
const func = (person) => {};
func(person);
// 중괄호를 이용해서 매개 변수를 받는다.
// 객체를 이용했기 때문에 이렇게 받을 수 있음..
const func2 = ({ name, age, hobby, extra }) => {
console.log(name, age, hobby, extra);
};
func(person);
Spread 연산자와 Rest 매개변수
💡rest매개변수를 사용할 때,
...rest에서 rest가 아니라 다른 변수명을 써도 된다!! ...처럼 점 3개만 붙으면 rest 매개변수라는 의미가 된다.
// 1. Spread 연산자
// -> Spead : 흩뿌리다, 펼치다 라는 뜻
// -> 객체나 배열에 저장된 여러개의 값을 개별로 흩뿌려주는 역할
let arr1 = [1, 2, 3];
let arr2 = [4, 5, 6];
// arr1배열을 arr2배열에 넣고 싶을 때
let arr2_1 = [4, arr1[0], arr1[1], arr1[2], 5, 6];
// 이런식으로 할 수 있지만, arr[1]값이 사라질 수도 있는거임.
// 가장 큰 문제는 하나하나씩 넣어주기 귀찮다는거임!!!
// 해결: spread연산자 활용
// ...arr1: arr1을 ...(흩뿌려주겠다!!)
let arr2_2 = [4, ...arr1, 5, 6];
// # 활용1: 객체에서도 활용 가능
let obj1 = {
a: 1,
b: 2,
};
let obj2 = {
...obj1, // obj1을 흩뿌려주겠다
c: 3,
d: 4,
};
console.log(obj2);
// # 활용2: 함수에서 활용 가능
function funcA(p1, p2, p3) {
// 매개변수 3개를 받는다.
console.log(p1, p2, p3);
}
funcA(...arr1);
// 2. Rest 매개변수
// -> Rest는 나머지, 나머지 매개변수
// 여러개의 매개변수를 받아야 할 때 배열 형태로 한 방에 여러 개의 매개변수를 받아올 수 있음
// ** rest가 아니라 다른 변수명을 써도 된다!! ...처럼 점 3개만 붙으면 rest 매개변수라는 의미가 된다.
function funcB(...rest) {
// 한 방에 매개변수를 받겠다!
// 매개변수를 정의하는 공간인 소괄호 안에서 사용이 되었기 때문에, spread연산자가 아니라 rest 매개변수라는 점을 주의!
console.log(rest);
}
funcB(...arr1);
// # 활용1
function funcC(one, two, ...rest) {
// 한 방에 매개변수를 받겠다!
// 매개변수를 정의하는 공간인 소괄호 안에서 사용이 되었기 때문에, spread연산자가 아니라 rest 매개변수라는 점을 주의!
console.log(one); // 첫 번째 매개변수를 꼭 다른 이름으로 받고 싶다고 하면, one을 추가해서 받으면됨..이후 나머지가 rest에 들어간다
console.log(two); // 두 번째 매개변수를 받고 보여줌!
console.log(rest);
}
funcC(...arr1);
// #주의
// ...rest의 의미가 나머지 매개변수를 다 받겠다. 라는 뜻이라서
// ...rest 뒤에는 매개변수가 더 이상 올 수없다.
// function funcD(one, ...rest, three) {
// console.log(one);
// console.log(rest);
// }
// funcD(...arr1);
원시타입 vs 객체타입
왜 원시타입 vs 객체타입으로 나누었을까?
원시타입과 객체타입은 값이 저장되거나 복사되는 과정이 서로 다르기 때문이다.
주의사항 1. 객체 타입을 다룰 때 의도치 않게 값이 수정되는 상황 발생될 수 있다.
복사한 값(o2)를 수정했는데 원본의 값(o1)의 값도 함께 수정될 수 있다.
-> 값이 수정되는 것을 의도하지 않았거나 수정되었다는 것 자체를 모른다면 큰 오류가 발생할 수도 있다..(Side Effect)
💡Side Effect
의도하지 않았는데 하나의 변화가 또 다른 변수의 변화를 가져오는
해결방법
변수의 참조값 자체를 복사하도록 하는게 아니라 새로운 객체를 생성하고 그 내부에 ...o1처럼 spread연산자 등을 이용해서 새로운 객체를 생성하면서 내부 프로퍼티만 따로 복사해는 방식 활
주의사항 2. 객체간의 비교는 기본적으로 참조값을 기준으로 이루어진다.
o2는 얕은 복사(객체의 참조값을 복사함)
o3는 깊은 복사(새로운 객체를 생성하면서 프로퍼티만 따로 복사함)
console.log(o1 === o2) >> true
console.log(o1 === o3) >> false
console.log(o1 === o3)도 true로 나오게 하려면?
JSON.stringify를 활용
💡JSON.stringify : 객체를 문자열로 변환
주의사항 3. 배열과 함수도 사실 객체이다.
배열과 함수가 사실 객체였고, 그러므로 추가적인 프로퍼티나 메서드를 가질 수 있다.
나머지는 향후 챕터에서 알아보도록 한다..
반복문은로 배열과 객체 순회하기
순회(Iteration)이란?
배열, 객체에 저장된 여러개의 값에 순서대로 하나씩 접근하는 것을 말함
💡for of vs for in
for of 반복문: 배열에서만 사용 가능
비슷한 for in은 객체에서만 사용 가능
# 안헷갈리게 외우는 방법..
객체 프로퍼티 확인하는 연산자 중에 in이 있음
이처럼 in은 객체에 사용하는 것이다!!!
const obj = { name: "Alice", age: 25 }; console.log("name" in obj); // true console.log("address" in obj); // false
// 1. 배열 순회
let arr = [1, 2, 3];
// 1. 배열 인덱스
for (let i = 0; i < arr.length; i++) {
// console.log(arr[i]);
}
// 1.2 for of 반복문: 배열에서만 사용 가능(비슷한 for in은 객체에서만 사용 가능)
for (let item of arr) {
// console.log(item[0]); // 이건 undefined로 나옴..
// console.log(item);
}
// 배열 인덱스와 for of 반복문의 차이점은 배열인덱스를 사용하면 나중에 인덱스로 뭔가 할 수 있음..
// 2. 객체 순회
let person = {
name: "ㅇㅇㅇ",
age: 27,
hobby: "테니스",
};
// 2.1 Object.keys 사용
// -> 객체에서 key 값들만 뽑아서 새로운 배열로 반환
let keys = Object.keys(person);
// console.log(keys); // keys 값만 반환
for (let key of keys) {
// 인덱스쓰고 싶으면 기본 for문 사용하면 됨..
const value = person[key]; // 이렇게 하면 vaule값도 가져올 수 있음..
// console.log(key, value);
}
// 2.2 Object.vaules
// -> 객체에서 value 값들만 뽑아서 새로운 배열로 반환
let vaules = Object.values(person);
console.log(vaules);
for (let value of vaules) {
console.log(value);
}
// 2.3 for in: for of와 비슷, 객체만들 위해 존재하는 특수한 반복문
for (let key in person) {
const value = person[key];
console.log(key, value);
}
배열메서드 1. 요소 조작
💡 push, pop / shift, unshift
// push, pop보다 shift, unshift 메서드가 느리다..// -> 왜? push, pop은 가장 마지막에 한 값으로 하는 거임. shift, unshift는 맨 앞값이라서 뒤에서 부터 앞까지 다 읽어온 다음에 처리해야함.
💡slice
// 주의: 앞에서 부터 자를 때(양수를 넣을 때)는 인덱스로 한다.// 뒤에서 부터 자를 때는(음수를 넣을 때)는 자를 개수로 한다.
// 6가지의 요소 조작 메서드
// 1. push
// 배열의 맨 뒤에 새로운 요소를 추가하는 메서드
let arr1 = [1, 2, 3];
// const newLength = arr1.push(4, 5, 6); // 길이를 반환함
// console.log(newLength); // >> 6
// 2. pop
// 배열의 맨 뒤에 있는 요소를 제거하고, 반환
let arr2 = [1, 2, 3];
const popedItem = arr2.pop();
// console.log(popedItem); // 뽑힌 아이템 >> 3
// console.log(arr2);
// 3. shift
// 배열의 맨 앞에 있는 요소를 제거, 반환
let arr3 = [1, 2, 3];
// const shiftedItem = arr3.shift(); // 맨 앞자리에 있는 아이템 나옴 >> 1
// console.log(shiftedItem, arr3);
// 4. unshifs
// 배열의 맨 앞에 새로운 요소를 추가하는 메서드
let arr4 = [1, 2, 3];
const newLength = arr4.unshift(0); // 맨 앞에 0을 추가하겠다.
// console.log(newLength); // 반환값은 길이임 >> 4
// console.log(arr4);
// push, pop보다 shift, unshift 메서드가 느리다..
// -> 왜? push, pop은 가장 마지막에 한 값으로 하는 거임. shift, unshift는 맨 앞값이라서 뒤에서 부터 앞까지 다 읽어온 다음에 처리해야함.
// 5. slice
// 마치 가위처럼, 배열의 특정 범위를 잘라내서 새로운 배열로 반환
let arr5 = [1, 2, 3, 4, 5];
const sliced = arr5.slice(2, 5); // 2번째 부터 4번째까지 잘라내기(2 <= x < 5), 마지막은 +1해서 적어줘야함..
const sliced2 = arr5.slice(2); // 마지막 매개변수를 안써주면, 마지막까지 잘라냄.
console.log(arr5); // 값이 배열에 바로 저장되는 것은 아님님
console.log(sliced);
console.log(sliced2);
let sliced3 = arr5.slice(-2); // 뒤에서부터 2개만 잘라냄냄
console.log(sliced3);
// 주의: 앞에서 부터 자를 때(양수를 넣을 때)는 인덱스로 한다.
// 뒤에서 부터 자를 때는(음수를 넣을 때)는 자를 개수로 한다.
// 5. concat
// 2개의 서로 다른 배열을 이어 붙여서 새로운 배열을 반환
let arr6 = [1, 2];
let arr7 = [3, 4];
let concatedArr = arr6.concat(arr7);
console.log(concatedArr);
배열 메서드2. 순회와 탐색
💡indexOf vs findInx : 인덱스를 찾는 건 동일한데 왜 indexOf랑 findIndex 둘다 필요한가?
요약: 원시타입 비교할 때는 indexOf // 객체 비교할 때는 findIdex
// console.log(objArr.indexOf({ name: "ㅇㅇㅇ" })); // >> -1 이 나오면서 못 찾음
// 왜? indexOf는 얖은 비교 사용하기 때문
// console.log(objArr.findIndex((item) => item.name === "ㅇㅇㅇ"));
// 콜백 함수를 이용해서 직접 프로퍼티 값을 기준으로 비교할 수 있다.
// 5가지 요소 순회 및 탐색 메서드
// 1. forEach
// 모든 요소를 순회하면서, 각각의 요소에 특정 동작을 수행시키는 메서드
let arr1 = [1, 2, 3];
arr1.forEach(function (item, idx, arr1) {
// function () 매개변수 이렇게 들어오는 거임.. => (현재 매개변수 값, 현재 반복 카운트, 전체 배열)
// console.log(idx, item * 2);
});
// 활용
let doubleArr = [];
arr1.forEach((item) => {
// arr1의 값을 한개씩 넣으면서 for문 돌림
doubleArr.push(item * 2);
// console.log(doubleArr);
});
// 2. includes
// 배열에 특정 요소가 있는지 확인하는 메서드
let arr2 = [1, 2, 3];
let isInclude = arr2.includes(3);
// console.log(isInclude); // >> true
// 3. indexOf
// 특정 요소의 인덱스(위치)를 찾아서 반환하는 메서드
// 존재하지 않는다면 -1을 반환함
let arr3 = [1, 2, 3];
const index = arr3.indexOf(2);
// console.log(index); // >> 1
// 4. findIndex
// 모든 요소를 순회하면서, 콜백함수를 만족하는(true를 반환하는)
// 특정 요소의 인덱스(위치)를 반환하는 메서드
// 만족하는 값이 없으면 -1 반환환
let arr4 = [1, 2, 3];
const findedIndex = arr4.findIndex((item) => {
if (item === 2) return true;
});
// 더 짧게 만드는 방법~~
const findedIndex3 = arr4.findIndex((item) => item === 2);
const findedIndex2 = arr4.findIndex((item) => {
if (item === 2) return true; // 2개 해놔도 반환값은 가장 빠른 1개만 나옴..
if (item % 2 !== 0) return true; // item===2를 만족 못했어도, 이미 1이 여기서 만족했기 때문.. 인덱스 값은 0이 나올 것임..
});
// true가 되는 값이 2인데, 2의 인덱스는 1임
// console.log(findedIndex); // >> 1
// console.log(findedIndex2);
// console.log(findedIndex3);
// 인덱스를 찾는 건 동일한데 왜 indexOf랑 findIndex 둘다 필요한가?
// indexOf는 원시 타입의 값이 들어있을 때가 아니라 객체 타입의 값들이 저장된 배열에는 정확한 요소의 위치를 찾아낼 수 없다.
let objArr = [{ name: "ㅇㅇㅇ" }, { name: "ㄴㄴㄴ" }];
// console.log(objArr.indexOf({ name: "ㅇㅇㅇ" })); // >> -1 이 나오면서 못 찾음
// 왜? indexOf는 얖은 비교 사용하기 때문
// console.log(objArr.findIndex((item) => item.name === "ㅇㅇㅇ")); // 콜백 함수를 이용해서 직접 프로퍼티 값을 기준으로 비교할 수 있다.
// 정리: 원시타입 비교할 때는 indexOf // 객체 비교할 때는 findIdex
// 5. find
// 모든 요소를 순회하면서 콜백함수를 만족하는 요소를 찾는데, 요소를 그대로 반환
let arr5 = [{ name: "ㅇㅇㅇ" }, { name: "ㄴㄴㄴ" }];
const finded = arr5.find((item) => item.name === "ㅇㅇㅇ");
console.log(finded);
배열 메서드 3. 배열 변형
💡join vs concat
join과 concat 둘다 합치는 거라 좀 헷갈렸다.
join은 배열의 요소를 문자열로 바꾸는 것이고
concat은 배열끼리 합치는 것이다.
join 예시
["hi","im"] => "hi im"
concat 예시
["hi"] ["im"] => ["hi", "im"]
// 5가지 배열 변형 메서드
// 1. filter
// 기존 배열에서 조건을 만족하는 요소들만 필터링하여 새로운 배열로 반환
let arr1 = [
{ name: "ㅇㅇㅇ", hobby: "테니스" },
{ name: "ㅅㅅㅅ", hobby: "테니스" },
{ name: "ㄷㄷㄷ", hobby: "독서" },
];
const tennisPeople = arr1.filter((item) => {
if (item.hobby === "테니스") return true;
});
// 안에 if문 안쓰고 바로 조건문만 써도 사용 가능
const tennisPeople2 = arr1.filter((item) => item.hobby === "테니스");
console.log(tennisPeople);
console.log(tennisPeople2);
// 2. map
// 배열의 모든 요소를 순회하면서, 각각 콜백함수를 실행하고 그 결과값들을 모아서 새로운 배열로 반환
let arr2 = [1, 2, 3];
const mapResult1 = arr2.map((item, idx, arr) => {
// (item, idx, arr) => {} // 이 부분이 콜백함수임..
// console.log(idx);
return item * 2; // return값도 나옴
});
// map 활용: 겍체에서 이름만 뽑기
let names = arr1.map((item) => item.name);
// console.log(names);
// 3. sort
// 배열을 "사전순으로" 정렬하는 메서드(숫자의 대소 비교가 아님)
// # 문자 정렬
let arr3 = ["b", "a", "c"];
arr3.sort();
console.log(arr3);
// # 숫자 정렬
// 숫자 정렬을 하고 싶다면 콜백함수에서 정렬 기준을 넘겨줘야함
let arr3_2 = [10, 3, 5];
// # 오름차순
// arr3_2.sort((a, b) => {
// if (a > b) {
// // b가 a 앞에 와라(오름차순)
// return 1; // -> b, a 배치
// } else if (a < b) {
// // a가 b앞에 와라
// return -1; // -> a, b 배치
// } else {
// // 두 값의 자리를 바꾸지 마라
// return 0; // a, b 자리를 그대로 유지지
// }
// });
// console.log(arr3_2);
// # 내림차순
arr3_2.sort((a, b) => {
if (a > b) {
// b가 a 앞에 와라(오름차순)
return -1; // -> b, a 배치
} else if (a < b) {
// a가 b앞에 와라
return 1; // -> a, b 배치
} else {
// 두 값의 자리를 바꾸지 마라
return 0; // a, b 자리를 그대로 유지지
}
});
console.log(arr3_2);
// 4. toSorted
// sort와 정렬 기능은 똑같음.
// sort는 원본 배열을 정렬함 / toSorted는 정렬된 '새로운 배열'을 반환하는 메서드
let arr5 = ["c", "a", "b"];
const sorted = arr5.toSorted();
console.log(arr5);
console.log(sorted);
// 5. join
// 배열의 모든 요소를 하나의 문자열로 합쳐서 변환하는 메서드
let arr6 = ["hi", "im", "winterlood"];
const joined = arr6.join("-"); // 구분자는 ,(콤마)가 아니라 다른 것을 넣고 싶으면, 괄호 안에 넣으면 됨..
console.log(joined);
Date 객체와 날짜
// 1. Date 객체를 생성하는 방법
let date1 = new Date(); // 생성자
// 매개변수 아무것도 안주면 현재시간 나옴..
console.log(date1);
let date2 = new Date("2000/01/01/10:10:10"); // "/" 대신 ".", "-" 다 가능.
let date3 = new Date(2000, 1, 1, 10, 10, 10); // , 도 가능
// 시간은 : 을 써야함..
console.log(date2);
console.log(date3);
// 2. 타임 스탬프
// 특정 시간이 "1970.01.01 00시 00분 00초"로 부터 몇 ms가 지났는지를 의미하는 숫자값
// "1970.01.01 00시 00분 00초"는 UTC라고 부름
// UTC: 세계 모든 나라가 표준으로 사용하는 시간이 시각되는 지점
let ts1 = date1.getTime(); // 현재 시간의 타임스탬프
console.log(ts1); // ms로 나옴
let date4 = new Date(ts1); // 타임스탬프에 해당하는 시간으로 Date객체 생성
console.log(date1, date4); // 날짜를 확인해보면, 현재 시간과 타임스탬프로 구한 시간이 동일한 것을 확인할 수 있다.
// 3. 시간 요소들을 추출하는 방법
let year = date1.getFullYear();
let month = date1.getMonth() + 1; // 주의: month는 0부터 시작이다(1월은 0이다.) 그래서 +1을 해서 사용하면 보기 좋다.
let date = date1.getDate();
let hour = date1.getHours();
let minute = date1.getMinutes();
let seconds = date1.getSeconds();
console.log(year, month, date, hour, minute, seconds);
// 4. 시간 수정하기
// 위에처럼 get을 set으로 바꿔주면 된다.
date1.setFullYear(2023);
date1.setMonth(2); // 주의: month는 0부터 시작이다(1월은 0이다.) 그렇다면 2는 3월이다.
date1.setDate(14);
date1.setHours(23);
date1.setMinutes(59);
date1.setSeconds(59);
// 5. 시간을 여러 포맷으로 출력
console.log(date1.toDateString()); // 시분초를 제외하고 현재날짜만 보여주고 싶을 때
console.log(date1.toLocaleDateString()); // 영어말고 현지화된 시간 포맷으로 출력
동기와 비동기
동기? 여러 개의 작업을 순서대로, 하나씩 처리하는 방식
* JavaScript는 "동기"적으로 코드를 실행한다.
동기 방식에는 치명적인 단점이 존재한다.
- 하나의 작업이 오래 걸린다면, 진행할 수가 없다.
-> 해결: 멀티 쓰레드 사용
-> 하지만, JavaScript 엔진에는 쓰레드가 1개 밖에 없음
비동기? 비동기적이지 않다. 작업을 순대로 처리하지 않는다.
각각의 작업들이 종료되었을 때 해당 작업의 결과 값을 이용해서 또 다른 작업을 수행해야 한다면,
JavaScript 에는 각각 작업에 콜백 함수를 붙여서 처리할 수 있다.
자, 그럼 어떻게 동시에 작업을 처리하는건가?
비동기 작업은 JavaScript 엔진이 아닌 Wep APIs에서 실행한다.
💡Wep APIs
웹 브라우저가 직접 관리하는 별도의 영역
// # 1번으로 실행행
console.log(1);
// # 2번으로 실행 -> Wep APIs에 요청(비동기)
// 코드를 특정 시간이 지난 이후에 비동기적으로 실행시켜주는 기능
setTimeout(() => {
console.log(2); // () => {console.log(2);} // 이걸 콜백 함수라고 부름
}, 3000); // 3초초 후에 실행
// # 3번으로 실행
console.log(3);
// # 4번 콜백함수 실행행
// 2번에서 실행했던 비동기적 요청을 여기서 실행함!
비동기 작업 처리 3가지 정리
1. 콜백 함수 (Callback Function)
의미: 콜백 함수는 다른 함수의 인자로 전달되어 특정 작업이 완료된 후 호출되는 함수입니다. 비동기 작업이 완료된 시점에 실행되도록 예약할 수 있습니다.
예시:
function fetchData(callback) {
setTimeout(() => {
const data = { name: "홍길동" };
callback(data);
}, 1000);
}
fetchData((data) => {
console.log(data); // 1초 후에 { name: "홍길동" } 출력
});
장점:
- 간단한 비동기 작업에 적합
- 다른 함수의 실행을 완료 후에 특정 작업을 수행할 수 있음
단점:
- 콜백 지옥(Callback Hell): 여러 개의 비동기 작업을 중첩해서 사용할 경우 코드가 복잡해지고 가독성이 떨어짐
2. Promise
의미: Promise는 비동기 작업의 완료(성공) 또는 실패(에러)를 나타내는 객체입니다. 비동기 작업의 결과를 더 쉽게 관리하고 체이닝(연속적인 작업)을 할 수 있도록 도와줍니다.
예시:
function fetchData(callback) {
setTimeout(() => {
const data = { name: "홍길동" };
callback(data);
}, 1000);
}
fetchData((data) => {
console.log(data); // 1초 후에 { name: "홍길동" } 출력
});
장점:
- 가독성이 높은 체이닝 가능
- 에러 처리가 용이
- 여러 비동기 작업을 병렬 또는 순차적으로 처리하기 용이
단점:
- 초기 학습 곡선이 있음
3. async & await
의미: async와 await는 Promise를 기반으로 한 비동기 코드를 동기 코드처럼 작성할 수 있게 해주는 문법입니다. 이를 통해 비동기 코드를 더 직관적이고 간결하게 작성할 수 있습니다.
예시:
function fetchData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
const data = { name: "홍길동" };
resolve(data);
}, 1000);
});
}
async function getData() {
try {
const data = await fetchData();
console.log(data); // 1초 후에 { name: "홍길동" } 출력
} catch (error) {
console.error(error);
}
}
getData();
장점:
- 비동기 코드를 동기 코드처럼 작성하여 가독성 향상
- 에러 처리가 더 직관적 (try-catch 사용)
- 복잡한 비동기 로직을 간결하게 표현 가능
단점:
- 여전히 비동기 처리의 기본 개념을 이해해야 함
왜 비동기 처리 방식일까?
비동기 처리 방식은 비동기 작업을 효율적으로 관리하기 위해 사용됩니다. 예를 들어, 네트워크 요청, 파일 읽기/쓰기, 타이머 등 시간이 걸리는 작업을 처리할 때, 동기 방식으로 처리하면 해당 작업이 완료될 때까지 프로그램의 다른 부분이 멈추게 됩니다. 이는 사용자 경험을 저해할 수 있습니다.
비동기 방식의 장점:
- 비차단(non-blocking): 긴 작업이 실행되는 동안에도 프로그램의 다른 부분이 계속 실행될 수 있음
- 효율적인 자원 사용: CPU나 메모리를 효율적으로 활용 가능
- 반응성 향상: 사용자 인터페이스가 응답성을 유지할 수 있음
비동기 작업 처리하기 1.콜백함수
💡콜백함수
다른 함수의 인수로 전달되어 특정 작업이 완료된 후 호출되는 함수입니다.
즉, 함수의 매개변수로 전달되어 특정 이벤트나 작업이 완료된 후 실행되는 함수를 말합니다.
콜백 함수는 동기적(synchronous) 또는 비동기적(asynchronous)으로 작동할 수 있습니다.
콜백함수 안에서 콜백함수를 계속 사용하면 indet(들여쓰기) 가 싶어지는 코드가 된다.
그럼 점점 가독성이 안좋아지고, 이걸 '콜백지옥'이라고 부른다.
// 비동기 작업을 하는 함수의 결과 값을 이 함수 외부에서 이용하고 싶다면,
// 콜백 함수를 사용해서 이 비동기 함수 안에서 콜백 함수를 호출하도록 설정
function add(a, b, callback) {
setTimeout(() => {
const sum = a + b;
callback(sum);
}, 3000);
}
add(1, 2, (value) => {
console.log(value);
});
// 활용1: 음식을 주문하는 상황
function orderFood(callback) {
setTimeout(() => {
const food = "떡볶이";
callback(food);
});
}
// 활용2: 메뉴가 뜨거우니까 식히기
function cooldownFood(food, callback) {
setTimeout(() => {
const cooldownedFood = `식은 ${food}`;
callback(cooldownedFood);
}, 2000);
}
// 활용3: 메뉴 얼리기
function freezeFood(food, callback) {
setTimeout(() => {
const freezeFood = `냉동된 ${food}`;
callback(freezeFood);
}, 1500);
}
orderFood((food) => {
console.log(food);
cooldownFood(food, (cooldownedFood) => {
console.log(cooldownedFood);
freezeFood(cooldownedFood, (freezeFood) => {
console.log(freezeFood);
});
});
});
2.13. 비동기 작업 처리하기 2. Promise
💡Promise란?
- 비동기 작업을 효율적으로 처리할 수 있도록 도와주는 자바스크립트의 내장 객체
- Promise는 비동기 작업을 감싸는 객체이다.
(여기서 다 배우면 너무 오래걸리기 때문에 1.비동기의 작업 실행, 2.비동기 작업 상태 관리, 3.비동기 작업 결과 저장 3가지를 집중적으로 공부하겠음..)
비동기 작업 실행하는 함수라는 뜻에서 executor라고 부른다.
executor함수에는 2가지의 매개변수가 전달된다.
1. resolve, 2. reject
resolve: 비동기 작업을 성공 상태로 바꾸는 함수
reject: 비동기 작업을 실 상태로 바꾸는 함수
인수로 Promise의 결과 값을 전달해줄 수 있다.
인수값이 PromiseResult값에 나타난다.
const promise = new Promise((resolve, reject) => {
// 비동기 작업 실행하는 함수를 executor라고 부른다.
// 지금 {} 괄호 안에 있는 것 executor라고 부른다.
// #상황1: resolve - 비동기 작업 성공으로 처리할 경우
// setTimeout(() => {
// console.log("안녕");
// resolve("성공했음..");
// }, 2000);
// #상황2: reject - 비동기 작업 실패로 처리할 경우
setTimeout(() => {
reject("실패했음..");
});
});
- resolve의 인수로 전달한 결과 값을 매개 변수로 사용 가능
// # 활용 방법
const promise = new Promise((resolve, reject) => {
const num = 10;
setTimeout(() => {
//num의 타입이 "number"일 경우
if (typeof num === "number") {
resolve(num + 10); // >> resolve(10 + 10)
} else {
reject("num이 숫자가 아닙니다.");
}
}, 2000);
});
// # 활용 방법 1-1 -> promise 결과값을 이용해보기(then 메서드 - resolve(비동기 작업 성공했을 때) 이후 함수)
// resolve의 인수로 전달한 결과 값을 매개 변수로 제공
promise.then((value) => {
console.log(value); // >> 20
});
// reject(비동기 작업 실패패했을 때) 이후
promise.catch((error) => {
console.log(error);
});
// # 활용 방법 1-2 -> Promise Chaining: Promise를 연결한다
// 합치는 것도 가능
// then메서드는 promise를 객체를 그대로 반환하기 때문에 catch 메서드를 함께 쓰느 것도 가능..
promise
.then((value) => {
console.log(value); // >> 20
})
.catch((error) => {
console.log(error);
});
// # 활용 방법 2 -> num 값을 동적으로 바꿔가면서 사용해보자
function add10(num) {
const promise = new Promise((resolve, reject) => {
// const num = 10; // 삭제
setTimeout(() => {
//num의 타입이 "number"일 경우
if (typeof num === "number") {
resolve(num + 10); // >> 아래의 result값이 = num + 10 이거임..
} else {
reject("num이 숫자가 아닙니다.");
}
}, 2000);
});
return promise;
}
const p = add10(0); // num = 0
p.then((result) => {
// result = 0(num) + 10
console.log(result); // >> 10
const newP = add10(result); // num = 10
newP.then((result) => {
// result = 10(num) + 10
console.log(result); // >> 20
// 이런 식으로 계속하면 콜백 지옥이 발생함..(계속 들여쓰기 해야함..)
// Promise는 콜백지옥을 방지하기 위해서 retrun값을 제공? 함
// 프로미스 객체를 반환하도록 해주면 이제는 이 then 메서드의 결과 값이 이 새로운 프로미스 객체(newP)가 된다.
});
});
// return 반환
p.then((result) => {
console.log(result);
const newP = add10(result);
return newP; // 여기 return
}).then((result) => {
console.log(result);
});
// 더 간결하게..
add10(0)
.then((result) => {
console.log(result);
return add10(result); // 여기 retrun값 받아서
})
.then((result) => {
// 여기서 사용
console.log(result);
// 여기 retrun값 받아서
return add10(undefined); // 만약 중간에서 오류가 발생한다면, 어디서 오류가 발생하더라도 마지막 catch메서드가 실행되어서 'num이 숫자가 아닙니다.'메시지가 뜬다.
})
.then((result) => {
// 여기서 사용
console.log(result);
return add10(result);
})
.catch((error) => {
console.log(error);
});
위의 코드 순서를 화살표로 표시해봤다..
2.14 비동기 작업 처리하기 3. Async & Await
// # async
// 어떤 함수를 비동기 함수로 만들어주는 키워드
// 함수가 Promise를 반환하도록 반환해주는 키워드
async function getData() {
return {
name: "ㅇㅇㅇ",
id: "winterlood",
};
}
// console.log(getData());
// --------
// 애초에 Promise를 반환하는 함수라면 async가 하는 역할을 하지 않지만, Promise가 없다면 async가 Promise가 반환되도록 한다.
async function getData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
name: "ㅇㅇㅇ",
id: "winterlood",
});
}, 1500);
});
}
// console.log(getData());
// # await
// async 함수 내부에서만 사용이 가능한 키워드
// 비동기 함수가 다 처리되기를 기다리는 역할
// async - await 를 쓰기 전에는 .then을 사용해서 값을 가져와야 했음..
// function printData() {
// getData().then((result) => {
// console.log(result);
// });
// }
// => async - await을 이용해 비동기 작업을 마치 동기 작업을 처리하는듯이 수행할 수 있다.
async function printData() {
const data = await getData();
console.log(data);
}
printData();
'공부 > frontend' 카테고리의 다른 글
React.js (0) | 2025.01.30 |
---|---|
Node.js 기초 (0) | 2025.01.29 |
JavaScript (2) | 2024.12.08 |
화면 구현 후기(코드아님): 컴포넌트 사용법 (1) | 2024.12.07 |
HTML, CSS (0) | 2023.06.14 |