[React 🔯] 02. JavaScript 심화
![[React 🔯] 02. JavaScript 심화](/images/useBlog/react.jpg)
해당 학습 정리는 이정환 강사님의 한입 크기로 잘라먹는 React기반으로 작성하였습니다.
Truthy & Falsy
자바스크립트는 어떠한 값이 boolean 타입에 해당하는 참이거나 또는 거짓이지 않아도 상황에 따라서 참으로 판단하거나 거짓으로 판단하는 경우가 있다.
if (123) {
console.log("123 is true"); // Truthy 한 값 (참 같은 값)
} else {
console.log("123 is false");
}
if (undefined) {
console.log("undefined is true");
} else {
console.log("undefined is false"); // Falsy 한 값 (거짓 같은 값)
}
자바스크립트의 모든 값은 Truthy 하거나 Falsy하다. 이를 이용하면 조건문을 간결하게 만들 수 있다.
예제
// 1. Falsy한 값
let f1 = undefined;
let f2 = null;
let f3 = 0;
let f4 = -0;
let f5 = NaN;
let f6 = "";
let f7 = 0n;
// 2. Truthy 한 값
// -> 7가지 Falsy 한 값을 제외한 나머지 모든 값
let t1 = "hello"; // 문자열
let t2 = 123; // 숫자
let t3 = []; // 빈 배열
let t4 = {}; // 빈 객체
let t5 = () => {}; // 함수
// 3. 활용 사례
/*
// 값이 없는 경우를 일일이 undefined인지, null인지 확인해야한다
function printName(person) {
if (person === undefined || person === null) {
console.log("person의 값이 없음")
}
console.log(person.name);
}
let person = { name: "이규현" };
printName(person);
let person or let person = null;
printName(person);
*/
function printName(person) {
// person이 Falsy(null, undefined 등)하면 !연산자에 의해 참이 되어 실행됨
if (!person) {
console.log("person의 값이 없음");
}
console.log(person.name);
}
let person = { name: "이규현" };
printName(person); // 결과: 이규현
예제 코드 설명
-
자바스크립트에서 아래의 7가지 값은 조건문에서
false로 판단한다. -
위에서 언급한 7가지 Falsy 한 값을 제외한 나머지 모든 값은
true로 판단한다. -
매개변수로 전달된 객체가 null이거나 undefined인 경우를 Falsy 특징을 이용해 한 번에 처리할 수 있다.
단락 평가 (short-circuit Evaluation)
단락 평가란?
AND나 또는 OR 같은 논리 연산식에서 이 첫 번째 피연산자의 값만으로도 해당 연산의 결과를 확정할 수 있다면 두 번째 피연산자의 값에는 아예 접근하지 않는 자바스크립트의 특징이다.
let varA = false;
let varB = true;
//And 연산자
console.log(varA && varB);
console.log(varB || varA);
예제
// 1. 단락 평가 간단
function returnFalse() {
console.log("False 함수");
return false;
}
function returnTrue() {
console.log("True 함수");
return true;
}
// AND 연산: 앞이 false면 뒤는 보지도 않고 false 확정
console.log(returnFalse() && returnTrue());
// OR 연산: 앞이 true면 뒤는 보지도 않고 true 확정
console.log(returnTrue() || returnFalse());
// 2. Truthy & Falsy
// 논리 연산식에, Truthy 하거나 Falsy한 값이 사용되었을 때
// 연산 결과가 그냥 Truthy 하거나 Falsy한 값 그 자체가 되어버린다.
function returnFalse() {
console.log("False 함수");
return undefined; // Falsy 한 값
}
function returnTrue() {
console.log("True 함수");
return 10; // Truthy 한 값
}
// AND 연산: 앞이 Truthy 하면 뒤의 값을 반환한다.
console.log(returnTrue() && returnFalse()); // 결과: undefined
// OR 연산: 앞이 Truthy 하면 바로 그 값을 반환한다.
console.log(returnTrue() || returnFalse()); // 결과: 10
// 3. 단락 평가 활용 사례
function printName(person) {
// person이 있으면 person.name을, 없으면 Falsy 한 person을 name에 할당한다.
const name = person && person.name;
// name이 존재하면 name을 출력하고, 없으면 뒤의 문자열을 출력한다.
console.log(name || "person의 값이 없음");
}
printName(); // 결과: "person의 값이 없음"
printName({ name: "이규현" }); // 결과: "이규현"
예제 코드 설명
-
논리 연산자의 우선순위에 따라 결과가 확정되면 뒤쪽의 함수는 아예 호출되지 않는다.
-
논리 연산식에 Truthy 하거나 Falsy 한 값이 사용되면, 연산 결과는 Boolean 값이 아닌 해당 값 그 자체가 된다.
-
단락 평가를 이용하면 if문을 길게 쓰지 않고도 null이나 undefined 예외 처리를 간결하게 할 수 있다.
구조 분해 할당
구조 분해 할당이란?
배열이나 객채에 저장된 여러 개의 값들을 말 그대로 분해해서 각각 다른 변수에 할당하는 문법이다.

1. 배열의 구조 분해 할당
배열의 요소들을 개별 변수에 담을 때, 기존의 번거로운 방식 대신 사용하는 간결한 문법이다.
예제
// 1. 배열의 구조 분해 할당
let arr = [1, 2, 3];
/* [기존방식] 배열 선언 및 할당
let one = arr[0];
let two = arr[1];
let three = arr[2];
*/
// 배열의 구조 분해 할당 적용
let [one, two, three] = arr;
console.log(one, two, three);
// 특정 원소를 제외하고 할당하고 싶으면 변수명을 생략할 수 있다.
let [onee, twoo] = arr;
console.log(one, two);
// 배열의 원소의 개수를 넘어서 four라는 변수를 추가로 선언해도 오류가 발생하진 않음
let [oneee, twooo, threee, four] = arr;
console.log(oneee, twooo, threee, four); // four - undefined 출력
// 기본값을 설정하는 것도 가능하다.
let [oneeee, twooo0, threeee, fourr = 4] = arr;
console.log(oneeee, twoooo, threeee, four); // four - 4 출력
예제 코드 설명
-
기존 방식에서는 배열의 각 요소에 접근하기 위해 인덱스를 사용하여 하나씩 변수에 할당해야 했다. 구조 분해 할당을 사용하면 대괄호
[]를 이용해 배열의 원소를 순서대로 한 번에 할당할 수 있어 코드가 매우 간결해진다. -
배열의 모든 원소를 변수에 담을 필요는 없다. 필요한 개수만큼만 변수를 선언하면 앞의 원소부터 순서대로 할당되며, 저장하고 싶지 않은 원소는 변수명을 생략하여 무시할 수 있다.
-
배열의 실제 원소 개수보다 더 많은 변수를 선언하더라도 오류가 발생하지 않는다. 값이 매칭되지 않는 변수에는 자동으로
undefined가 할당된다. 또한, 할당될 값이 없을 경우를 대비해=을 사용하여 기본값을 미리 설정할 수도 있다.
2. 객체의 구조 분해 할당
예제
// 2. 객체의 구조 분해 할당
let person = {
name: "이규현",
age: 26,
hobby: "야구",
};
/* [기존 방식] 객체 할당
let userName = person.name;
let userAge = person.age;
let userHobby = person.hobby
console.log(userName,userAge,userHobby);
*/
// 객체의 구조 분해 할당
let { userName, useAge, userHobby } = person;
// 존재하지 않는 프로퍼티를 구조 분해할 수도 있다.
let { userNamee, userAgee, userHobbyy, extra } = person; // extra -> undefined 출력
// 기본값을 설정하는 것도 가능하다.
let { userNameee, userAgeee, userHobbyyy, extraa = "hello" } = person;
/*----------------------*/
// 3. 객체 구조 분해 할당을 이용해서 함수의 매개변수를 받는 방법
const func = ({ name, age, hobby, extra }) => {
console.log(name, age, hobby, extra);
};
func(person);
예제 코드 설명
-
기존에는 객체의 특정 값에 접근하기 위해
점 표기법(.)을 사용하여 변수마다 하나씩 할당해야 했다. 구조 분해 할당을 사용하면 중괄호{}를 이용해 객체의 키와 동일한 이름의 변수에 값을 한 번에 담을 수 있다. -
객체에 존재하지 않는 키 이름을 변수로 선언하면 undefined가 할당된다. 배열과 마찬가지로 =을 통해 기본값을 설정해두면, 해당 프로퍼티가 없을 때 지정한 값이 대신 들어간다.
-
함수에서 객체를 인수로 받을 때, 매개변수 부분에서 바로 구조 분해 할당을 적용하면 내부 코드가 훨씬 간결해진다.
Spread 연산자 & Rest 매개변수
Spread 연산자
Spread 연산자란??
객체나 배열에 저장된 여러개의 값을 개별로 흩뿌려주는 역할을 하는 연산자이다.
예제
// 1. Spread 연산자
// -> Spread : 흩뿌리다, 펼치다 라는 뜻
// -> 객체나 배열에 저장된 여러개의 값을 개별로 흩뿌려주는 역할
// 배열에서의 활용
let arr1 = [1, 2, 3];
// [기존 방식] 인덱스를 일일이 지정하여 나열함
// let arr2 = [4, arr1[0], arr1[1], arr1[2], 5, 6];
// [Spread 연산자 활용] ...을 사용하여 arr1의 모든 원소를 펼쳐서 넣음
let arr2 = [4, ...arr1, 5, 6];
console.log(arr2); // [4, 1, 2, 3, 5, 6] 출력
// 객체에서 활용
let obj1 = {
a: 1,
a: 2,
};
let obj2 = {
...obj1, // obj1의 프로퍼티(a: 1, b: 2)를 그대로 흩뿌림
c: 3,
d: 4,
};
// 함수의 인수로 활용
function funcA(p1, p2, p3) {
console.log(p1, p2, p3);
}
// arr1의 원소 [1, 2, 3]을 각각 p1, p2, p3에 매칭하여 전달함
funcA(...arr1); // {a: 1, b: 2, c: 3, d: 4} 출력
예제 코드 설명
-
배열 내부에 다른 배열의 원소들을 하나씩 꺼내어 넣어야 할 때, 인덱스로 일일이 접근하지 않고도 간편하게 합칠 수 있다.
-
기존 객체의 프로퍼티들을 그대로 가져오면서 새로운 프로퍼티를 추가하거나 수정할 때 유용하다.
-
배열의 원소들을 함수의 매개변수에 순서대로 전달하고 싶을 때 사용한다.
Rest 매개변수
Rest 매개변수란??
Rest는 '나머지'라는 뜻으로, 함수로 전달된 여러 개의 인수들을 하나의 배열로 묶어서 받아오는 문법이다. Spread 연산자가 값을 펼치는 역할이라면, Rest 매개변수는 반대로 값을 모아주는 역할을 한다.
예제
// 2. Rest 매개변수
// -> Rest는 나머지, 나머지 매개변수
let arr1 = [1, 2, 3];
// 모든 인수를 하나의 배열로 모으기
function funcB(...rest) {
console.log(rest);
}
funcB(...arr1);
// 특정 인수만 따로 받고 나머지만 모으기
function funcC(one, ...rest) {
console.log(one); // 1
consoloe.log(rest); // [2,3]
}
funcC(...arr1);
// 여러 개의 인수를 제외한 나머지만 모으기
function funcD(one, two, ...rest) {
// 1, 2는 각각 변수에 할당되고 남은 3만 배열에 담긴다.
console.log(rest); // [3]
}
funcD(...arr1);
// 매개변수 이름은 자유롭게 지정 가능
function funcF(...ds) {
console.log(ds);
}
funcF(...arr1);
예제 코드 설명
-
나머지 매개변수의 역할 배열로 통합: 함수 호출 시 전달된 여러 개의 독립된 인수들을 함수 내부에서 하나의 배열로 다룰 수 있게 해준다. 유연한 매칭: 앞에 일반 매개변수를 선언하면, 순서대로 값을 할당한 뒤 남은 나머지 인수들만 묶어서 가져온다.
-
사용 시 주의사항 마지막 위치: Rest 매개변수는 항상 매개변수 리스트의 가장 마지막에 위치해야 한다. 앞이나 중간에 올 경우 자바스크립트는 어디까지가 나머지인지 판단할 수 없어 에러가 발생한다. 이름 지정: 매개변수 이름은
...rest뿐만 아니라...ds처럼 개발자가 자유롭게 정할 수 있다.
원시타입과 객체타입
원시타입과 객체타입으로 나누는 이유
원시타입과 객체타입은 값이 저장되거나 복사되는 과정이 서로 다르기 때문이다.
원시타입
Number, String, Boolean 등 ... 값 자체로써 변수에 저장되고 복사된다.
불변값이다. (메모리 값 수정 X)

객체타입
Object, Array, Function 등... 참조값을 통해 변수에 저장하고 복사된다. 가변값이다. (메모리 값 수정 O)
객체 타입 주의사항 1. 의도치 않게 갚이 수정될 수 있다.

얕은 복사 vs 깊은 복사
- 얕은 복사 객체의 참조값을 복사한다. (원본 객체가 수정될 수 있어 위험하다.)
- 깊은 복사
새로운 객체를 생성하면서 프로퍼티만 따로 복사한다. (원본 객체가 수정될 일이 없어 안전하다.)

객체 타입 주의사항 2. 객차간의 비교는 기본적으로 참조값을 기준으로 이루어진다.

JSON.stringify() - 객체를 문자열로 변환하는 기능을 하는 자바스크립트 내장함수이다.
얕은 비교 vs 깊은 비교
-
얕은 비교 참조값을 기준으로 비교한다.
-
깊은 비교 객체를 문자열로 변환하여 비교한다. (JSON.stringify 등의 내장 함수를 이용해야 한다.)

객체 타입 주의사항 3. 배열과 함수도 사실 객체이다.

반복문으로 배열과 객체 순회하기
순회(Iteration)란?
배열, 객체에 저장된 여러개의 값에 순서대로 하나씩 접근하는 것을 말한다.
배열 순회
// 1. 배열 순회
let arr = [1, 2, 3];
// 1.1 배열 인덱스
for (let i = 0; i < arr.length; i++) {
console.log(arr[i]);
}
let arr2 = [4, 5, 6, 7, 8];
for (let i = 0; i < arr2.length; i++) {
console.log(arr2[i]);
}
// 1.2 for of 반복문
for (let item of arr) {
console.log(item); // 1,2,3 출력
}
기존 방식: arr.length를 확인하고 인덱스 변수 i를 조작하여 접근해야 하므로 코드가 다소 길다.
개선된 방식: for...of를 사용하면 인덱스 관리 없이 값 자체에 집중할 수 있어 가독성이 훨씬 높아진다.
객체 순회
// 2. 객체 순회
let person = {
name: "이정환",
age: 27,
hobby: "테니스",
};
// 2.1 Object.keys 사용
// -> 객체에서 key 값들만 뽑아서 새로운 배열로 반환한다.
let keys = Object.keys(person);
for (let key of keys) {
const value = person[key];
console.log(key, value);
}
// 2.2 Object.values
// -> 객체에서 value 값들만 뽑아서 새로운 배열로 반환한다.
let values = Object.values(person);
for (let value of values) {
// console.log(value);
}
// 2.3 for in
for (let key in person) {
const value = person[key];
console.log(key, value);
}
배열화 순회: Object.keys나 Object.values를 사용하면 객체의 정보를 배열 형태로 바꿀 수 있어, 배열이 가진 다양한 메서드들을 함께 활용할 수 있다는 장점이 있다.
for...in의 간결함: 별도의 배열 생성 과정 없이 객체 내부를 바로 훑을 수 있어 매우 편리하다.
배열 메서드 1. 요소 조작
1. push
배열의 맨 뒤에 새로운 요소를 추가하는 메서드이다.
let arr1 = [1, 2, 3];
// 1. 단일 요소 추가
arr1.push(4); // arr1 맨 뒤에 4 추가
console.log(arr1); // [1, 2, 3, 4]
// 2. 여러 요소 동시에 추가
arr1.push(5, 6, 7, 8);
console.log(arr1); // [1, 2, 3, 4, 5, 6, 7, 8]
// 3. 반환값 확인
// push 메서드는 요소를 추가한 후, 변경된 배열의 새로운 길이를 반환한다.
const newLength = arr1.push(9, 10, 11, 12);
console.log(newLength); // 12 (새로운 배열의 길이)
console.log(arr1); // [1, 2, ..., 12]
-
요소 추가 방식 기존에는 배열의 마지막에 값을 넣으려면 arr[arr.length] = 값과 같이 인덱스를 직접 계산해야 했으나, push를 사용하면 자동으로 맨 뒤에 데이터가 쌓인다. 콤마(,)를 이용해 여러 개의 인수를 한 번에 전달하면, 전달한 순서대로 배열의 끝에 추가된다.
-
반환값의 특징 push 메서드를 실행하면 단순히 배열이 수정되는 것뿐만 아니라, 연산이 끝난 시점의 **배열의 전체 길이(length)**를 결과값으로 돌려준다. 이를 활용해 추가 작업 후의 데이터 개수를 즉시 변수에 담아 사용할 수 있다.
2. pop
배열의 맨 뒤에 있는 요소를 제거하고 반환하는 메서드이다.
let arr2 = [1, 2, 3];
// 1. 맨 뒤 요소 제거 및 반환
// 배열의 마지막 요소인 3을 삭제하고, 그 값을 popedItem에 저장한다.
const popedItem = arr2.pop();
console.log(popedItem); // 3 (제거된 요소)
console.log(arr2); // [1, 2] (요소가 제거된 배열)
-
요소 제거 방식 별도의 인덱스를 지정하지 않아도 언제나 배열의 가장 마지막에 위치한 요소를 찾아내어 삭제한다. 메서드 호출 시 원본 배열에서 요소가 실제로 삭제되어 배열의 길(length)이 줄어든다.
-
반환값의 특징 pop은 단순히 삭제만 하고 끝나는 것이 아니라, 방금 삭제한 그 값을 결과값으로 돌려준다. 이 특징을 이용하면 배열에서 빼낸 데이터를 다른 변수에 담거나, 다른 로직에 즉시 전달하여 활용할 수 있다.
3. shift
배열의 맨 앞에 있는 요소를 제거하고 반환하는 메서드이다.
let arr3 = [1, 2, 3];
// 1. 맨 앞 요소 제거 및 반환
// 배열의 첫 번째 요소인 1을 삭제하고, 그 값을 shiftedItem에 저장한다.
const shiftedItem = arr3.shift();
console.log(shiftedItem); // 1 (제거된 요소)
console.log(arr3); // [2, 3] (요소가 제거된 배열)
-
요소 제거 방식 인덱스를 지정하지 않아도 언제나 배열의 0번 인덱스에 위치한 데이터를 삭제한다. 맨 앞의 요소가 사라지면 뒤에 있던 모든 요소의 인덱스가 하나씩 앞으로 당겨진다. 이 과정에서 배열 전체를 수정하므로 데이터가 많을 경우 pop보다 처리 속도가 느릴 수 있다는 점이 특징이다.
-
반환값의 특징 pop과 마찬가지로 방금 삭제한 첫 번째 요소를 결과값으로 돌려준다. 이를 활용해 큐(Queue) 구조를 구현하거나 맨 앞의 우선순위 데이터를 처리할 때 유용하게 쓰인다.
4. unshift
배열의 맨 앞에 새로운 요소를 추가하는 메서드이다.
let arr4 = [1, 2, 3];
// 1. 맨 앞에 단일 요소 추가
// 0을 배열의 가장 앞에 추가한다.
arr4.unshift(0);
console.log(arr4); // [0, 1, 2, 3]
// 2. 반환값 확인
// unshift 메서드는 요소를 추가한 후, 변경된 배열의 새로운 길이를 반환한다.
const newLength2 = arr4.unshift(-1);
console.log(newLength2); // 5 (새로운 배열의 길이)
console.log(arr4); // [-1, 0, 1, 2, 3]
-
요소 추가 방식 인덱스를 직접 조작하지 않아도 새로운 데이터를 배열의 가장 첫 번째 칸(0번 인덱스)에 삽입한다. 새로운 요소가 맨 앞에 들어오면 기존에 있던 모든 요소의 인덱스가 하나씩 뒤로 밀려나게 된다. 이 과정 때문에 배열의 크기가 클수록 push보다 연산 속도가 느려질 수 있다는 특징이 있다.
-
반환값의 특징 push와 동일하게 작업을 마친 후의 **배열 전체 길이(length)**를 결과값으로 돌려준다. 이를 통해 추가 직후 배열에 담긴 데이터의 총 개수를 바로 파악할 수 있다.
5. slice()
마치 가위처럼, 배열의 특정 범위를 잘라내서 새로운 배열로 반환하는 메서드이다.
let arr5 = [1, 2, 3, 4, 5];
// 1. 시작 인덱스와 끝 인덱스 지정
// 인덱스 2번부터 5번 전(인덱스 4번)까지 잘라낸다.
let sliced = arr5.slice(2, 5);
console.log(sliced); // [3, 4, 5]
// 2. 시작 인덱스만 지정
// 인덱스 2번부터 배열의 끝까지 잘라낸다.
let sliced2 = arr5.slice(2);
console.log(sliced2); // [3, 4, 5]
// 3. 음수 인덱스 사용
// 뒤에서부터 2개의 요소를 잘라낸다.
let sliced3 = arr5.slice(-2);
console.log(sliced3); // [4, 5]
// 4. 원본 배열 확인
// slice는 원본 배열을 변경하지 않는다.
console.log(arr5); // [1, 2, 3, 4, 5]
-
범위 지정 방식 첫 번째 인수는 시작 인덱스, 두 번째 인수는 종료 인덱스를 의미한다. 이때 주의할 점은 종료 인덱스로 지정한 **'바로 앞'**까지만 잘라낸다는 점이다. 두 번째 인수를 생략하면 마지막 요소까지 포함하며, 음수를 사용하면 배열의 끝에서부터 거꾸로 계산하여 범위를 지정할 수 있다.
-
원본 보존의 특징 push나 pop과 달리 원본 배열을 직접 수정하지 않고 새로운 배열 객체를 생성하여 반환한다. 따라서 원본 데이터를 안전하게 유지해야 할 때 매우 유용하다.
6. concat
두개의 서로 다른 배열을 이어 붙여서 새로운 배열로 반환한다.
let arr6 = [1, 2];
let arr7 = [3, 4];
// arr6 배열 뒤에 arr7 배열을 이어 붙인다.
let concatedArr = arr6.concat(arr7);
console.log(concatedArr); // [1, 2, 3, 4]
-
배열 결합 방식 메서드를 호출한 원본 배열이 앞에 오고, 인수로 전달한 배열이 그 뒤에 순서대로 결합된다. 하나의 배열뿐만 아니라 여러 개의 배열이나 개별 값들을 인수로 넘겨 한꺼번에 합치는 것도 가능하다.
-
원본 보존의 특징 slice와 마찬가지로 기존의 arr6와 arr7 배열을 직접 수정하지 않는다. 대신 두 배열의 요소를 복사하여 결합한 새로운 배열을 생성하기 때문에 데이터 손상 우려 없이 안전하게 사용할 수 있다.
배열 메서드 2. 순회와 탐색
1. forEach()
배열의 모든 요소를 순회하면서, 각각의 요소에 대해 특정 동작(콜백 함수)을 수행시키는 메서드다.
let arr1 = [1, 2, 3];
// 1.1 일반 함수를 이용한 순회
// (item: 현재 요소, idx: 현재 인덱스, arr: 전체 배열)
arr1.forEach(function (item, idx, arr) {
console.log(idx, item * 2);
});
// 1.2 화살표 함수를 이용한 순회 및 활용
let doubledArr = [];
arr1.forEach((item) => {
doubledArr.push(item * 2); // 각 요소를 2배로 만들어 새로운 배열에 추가
});
console.log(doubledArr); // [2, 4, 6]
배열의 모든 요소를 처음부터 끝까지 하나씩 꺼내어 콜백 함수를 실행한다.
현재 요소(item), 현재 인덱스(idx), 그리고 순회 중인 배열 전체(arr)를 인자로 받아 자유롭게 활용할 수 있다.
단순히 배열의 내용을 출력하거나, 예제처럼 외부 배열에 가공된 값을 담는 등의 반복적인 동작을 수행할 때 유용하다.
2. includes()
배열에 특정 요소가 있는지 확인하는 메서드이다.
let arr2 = [1, 2, 3];
// 배열에 숫자 3이 있는지 확인
let isIncluded = arr2.includes(3); // 존재하므로 true
// 배열에 숫자 10이 있는지 확인
let isIncluded2 = arr2.includes(10); // 존재하지 않으므로 false
console.log(isIncluded);
console.log(isIncluded2);
배열에 특정 값이 포함되어 있는지 확인하여 true 혹은 false를 반환한다.
indexOf를 사용해 -1과 비교하는 방식보다 가독성이 좋아 조건문에서 자주 사용된다.
3. indexOf()
특정 요소의 인덱스(위치)를 찾아서 반환하는 메서드다.
let arr3 = [1, 2, 3];
// 요소 '2'가 있는 위치(인덱스) 찾기
let index = arr3.indexOf(2); // 1
// 존재하지 않는 요소 '20' 찾기
let index2 = arr3.indexOf(20); // -1 (찾지 못함)
console.log(index);
console.log(index2);
// 중복된 요소가 있을 때
let arr4 = [2, 2, 2];
let index3 = arr4.indexOf(2); // 가장 앞에 있는 0 반환
console.log(index3);
찾으려는 값이 배열의 몇 번째 인덱스에 위치하는지 번호를 반환한다.
배열에 없는 값을 찾으라고 하면 -1을 반환하는 특징이 있다.
배열 내에 동일한 값이 여러 개 있다면, 가장 처음에 발견된(가장 낮은 인덱스) 위치를 반환한다.
4. findIndex()
모든 요소를 순회하며 콜백 함수를 만족하는 특정 요소의 인덱스를 반환한다.
let arr5 = [1, 2, 3];
// 4.1 기본 조건문을 활용한 탐색
const findedIndex = arr5.findIndex((item) => {
if (item === 2) return true;
});
console.log(findedIndex); // 1
// 4.2 조건식(홀수 찾기)을 활용한 탐색
const findedIndex2 = arr5.findIndex((item) => {
if (item % 2 !== 0) return true;
});
console.log(findedIndex2); // 0 (1이 홀수이므로)
// 4.3 화살표 함수 축약형을 활용한 탐색
const findedIndex3 = arr5.findIndex((item) => item % 2 !== 0);
console.log(findedIndex3); // 0
단순한 값을 찾는 것을 넘어, 콜백 함수를 통해 복잡한 조건을 만족하는 요소의 위치를 찾는다.
배열을 순회하다가 콜백 함수가 true를 반환하는 가장 첫 번째 요소의 인덱스를 반환하고 순회를 종료한다.
indexOf() vs findeIndex()
let objectArr = [{ name: "이규현" }, { name: "왕곽봉" }];
// 1. indexOf는 객체 내부 값 비교 불가 (얕은 비교)
// 동일하게 생긴 객체라도 메모리 주소가 다르므로 찾지 못함
console.log(objectArr.indexOf({ name: "이규현" })); // -1
// 2. findIndex는 콜백 함수를 통해 내부 프로퍼티 비교 가능
console.log(objectArr.findIndex((item) => item.name === "이규현")); // 0
indexOf는 원시 타입 값의 일치 여부를 직접 비교하므로, 메모리 주소가 다른 객체 타입 내부의 값은 찾지 못하고 -1을 반환한다.
객체로 이루어진 배열에서 특정 조건의 데이터를 찾을 때는 반드시 콜백 함수 내에서 프로퍼티를 직접 비교할 수 있는 findIndex를 사용해야 한다.
5. find()
let arr6 = [{ name: "이규현" }, { name: "왕곽봉" }];
// name이 "이규현"인 첫 번째 객체 요소를 그대로 가져옴
const finded = arr6.find((item) => item.name === "이규현");
console.log(finded); // { name: "이규현" }
findIndex가 위치(인덱스)를 알려준다면, find는 조건을 만족하는 요소 그 자체를 통째로 반환한다.
특정 키 값을 가진 객체 데이터를 배열에서 바로 뽑아내어 사용해야 할 때 매우 유용하다.
배열 메서드 3. 배열의 변형
1. filter()
기존 배열에서 특정 조건을 만족하는 요소들만 따로 추출하여 새로운 배열로 반환하는 메서드다.
let arr1 = [
{ name: "이규현", hobby: "메이플" },
{ name: "왕곽봉", hobby: "메이플" },
{ name: "엄박봉", hobby: "피파" },
];
// 1.1 기본 조건문을 활용한 필터링
const maplePeople = arr1.filter((item) => {
if (item.hobby === "메이플") return true;
});
console.log(maplePeople); // [이규현, 왕곽봉] 객체 배열 반환
// 1.2 축약형 사용
const maplePeople2 = arr1.filter((item) => item.hobby === "메이플");
});
console.log(maplePeople2); // [이규현, 왕곽봉] 객체 배열 반환
콜백 함수가 true를 반환하는 요소들만 모아서 새 배열을 만든다.
화살표 함수에서 (item) => { item.hobby === "메이플" }처럼 중괄호를 쓰면 반환값이 undefined가 되어 빈 배열이 나온다. 값을 바로 반환하려면 중괄호를 아예 빼거나, 중괄호 안에 return을 써야 한다.
2. map()
배열의 모든 요소를 순회하며 콜백 함수를 실행하고, 그 결과값들을 모아서 새로운 배열로 반환하는 메서드다.
let arr2 = [1, 2, 3];
// 2.1 요소를 가공하여 새로운 숫자 배열 생성
const mapResult = arr2.map((item, idx, arr) => {
return item * 2;
});
console.log(mapResult); // [2, 4, 6]
// 2.2 객체 배열에서 특정 프로퍼티(이름)만 뽑아내기
let names = arr1.map((item) => item.name);
console.log(names); // ["이규현", "왕곽봉", "엄박봉"]
기존 배열의 요소를 내가 원하는 형태(값, 객체, UI 요소 등)로 1:1 매핑하여 변환한다.
forEach와 비슷해 보이지만, map은 실행 결과를 모아 새로운 배열을 만든다는 점이 결정적인 차이다.
3. sort()
배열의 요소를 정렬하는 메서드다. 기본적으로는 요소를 문자열로 취급하여 사전순으로 정렬한다.
// 3.1 문자열 정렬 (사전순)
let arr3 = ["b", "a", "c"];
arr3.sort();
console.log(arr3); // ["a", "b", "c"]
// 3.2 숫자 오름차순 정렬 (작은 것 -> 큰 것)
let arr4 = [10, 3, 4];
arr4.sort((a, b) => {
if (a > b)
return 1; // a가 더 크면 b를 앞으로 (순서 바꿈)
else if (a < b)
return -1; // a가 더 작으면 a를 앞으로 (순서 유지)
else return 0; // 같으면 유지
});
console.log(arr4); // [3, 4, 10]
// 3.3 숫자 내림차순 정렬 (큰 것 -> 작은 것)
let arr5 = [10, 3, 4];
arr5.sort((a, b) => {
if (a > b)
return -1; // a가 더 크면 a를 앞으로
else if (a < b)
return 1; // a가 더 작으면 b를 앞으로
else return 0;
});
console.log(arr5); // [10, 4, 3]
다른 메서드들과 달리 sort는 호출한 원본 배열을 직접 바꾼다. 원본 데이터가 중요할 경우 복사본을 만들어 사용해야 한다.
sort()만 쓰면 10이 3보다 앞에 온다(문자열 '1'이 '3'보다 먼저이기 때문). 따라서 숫자를 정렬할 때는 반드시 위와 같은 비교 함수가 필요하다.
4. toSorted()
최신 문법으로, sort()와 기능은 같지만 원본은 유지하고 정렬된 새 배열을 반환한다.
let arr6 = ["c", "a", "b"];
// 원본은 그대로 두고, 정렬된 결과만 새로운 배열로 받음
const sorted = arr6.toSorted();
console.log(arr6); // ["c", "a", "b"] (원본 유지)
console.log(sorted); // ["a", "b", "c"] (새로운 배열)
원본 배열을 보존해야 하는 최신 개발 트렌드(불변성 유지)에 아주 적합한 메서드다.
5. join()
배열의 모든 요소를 하나의 문자열로 합쳐서 반환하는 메서드다.
let arr7 = ["hi", "im", "kyulee"];
// 구분자(콤마)로 합치기
const joined = arr7.join();
console.log(joined); // "hi,im,kyulee"
배열에 흩어진 문자열 데이터를 하나의 문장이나 특정 형식의 텍스트로 만들 때 편리하다. 구분자를 주지 않으면 기본적으로 콤마(,)가 들어간다.
Date 객체와 날짜
자바스크립트 내장 객체인 Date를 이용하면 현재 시간을 가져오거나 특정 날짜를 계산하는 등 다양한 시간 관련 작업을 수행할 수 있다.
1. Date 객체 생성 및 타임스탬프
날짜 객체를 만드는 방법은 크게 세 가지가 있으며, 컴퓨터가 시간을 계산하는 기준인 '타임스탬프' 개념을 이해해야 한다.
// 1.1 Date 객체 생성
let date1 = new Date(); // 현재 시간으로 생성
let date2 = new Date("2001-05-02 10:10:10"); // 특정 날짜 문자열로 생성
let date3 = new Date(2001, 4, 2, 12, 11, 10); // 숫자로 생성 (월은 0부터 시작함에 주의)
// 1.2 타임스탬프 (Time Stamp)
// 1970.01.01 00:00:00(UTC)로부터 몇 밀리초(ms)가 지났는지 나타내는 값이다.
let ts1 = date1.getTime();
let date4 = new Date(ts1); // 타임스탬프 숫자를 넣어 해당 시간의 객체를 복사할 수 있다.
날짜 문자열을 넣을 때 하이픈(-), 점(.), 슬래시(/) 등을 모두 인식한다.
숫자로 날짜를 생성할 때 1월은 0, 12월은 11로 취급한다. 따라서 5월을 설정하려면 4를 입력해야 한다.
복잡한 날짜 비교나 연산을 할 때 객체 형태보다 숫자 형태인 타임스탬프를 사용하는 것이 훨씬 빠르고 정확하다.
2. 시간 요소 추출 및 수정
생성된 Date 객체에서 연, 월, 일 등 특정 정보만 뽑아내거나 원하는 날짜로 변경할 수 있다.
// 2.1 시간 요소 추출
let year = date1.getFullYear();
let month = date1.getMonth(); // 0(1월) ~ 11(12월) 반환
let date = date1.getDate();
let hour = date1.getHours();
let minute = date1.getMinutes();
let seconds = date1.getSeconds();
// 2.2 시간 수정하기 (set 메서드)
date1.setFullYear(2025);
date1.setMonth(5); // 6월로 설정됨
date1.setDate(2);
date1.setHours(16);
값을 가져올 때는 get..., 값을 수정할 때는 set... 메서드를 사용한다.
과거에 쓰던 getYear()는 1900년대를 기준으로 계산하는 버그가 있으므로, 반드시 4자리 연도를 반환하는 getFullYear()를 사용해야 한다.
3. 다양한 포맷으로 출력하기
객체 형태인 날짜 데이터를 사용자가 읽기 편한 문자열 형태로 변환하여 출력할 수 있다.
// 3.1 영문 포맷 출력 (날짜만)
console.log(date1.toDateString()); // 예: "Mon Jun 02 2025"
// 3.2 현지화 포맷 출력 (날짜 + 시간)
// 사용자의 설정에 따라 한국어 등의 환경에 맞춰 출력된다.
console.log(date1.toLocaleString()); // 예: "2025. 6. 2. 오후 4:10:59"
toDateString은 시/분/초를 제외한 날짜 정보만 영문으로 깔끔하게 보여준다.
toLocaleString은 국가별 표준 표기법에 맞춰 자동으로 포맷을 변경해 주기 때문에 웹 사이트 UI에 날짜를 표시할 때 매우 유용하다.
동기와 비동기
동기란?
여러 개의 작업을 순서대로, 하나씩 처리하는 방식이다.
A를 처리하고 B를 처리하고 C를 처리한다.

자바스크립트는 코드를 한 줄씩 실행하는 **싱글 스레드(일꾼이 한 명)**다.
만약 Task A 처리 시간 0.5초 / Task B 처리 시간 10초 / Task C 처리 시간 0.3초라고 가정을 하자. 자바스크립트는 싱글 스레드이기 때문에 Task C는 Task B가 처리되는 시간 10초동안 기다려야한다.
이러한 단점을 해결하기 위해서 자바스크립트의 비동기에 대한 개념을 확인해보자
비동기란?
동기와 반대로 여러 개의 작업이 들어와도 순서대로 끝날 때까지 기다리지 않고, 작업을 동시에 처리하는 방식이다.
예제를 통해서 확인해보자
예제
// 1. 동기
console.log(1);
console.log(2);
/* ----------------- */
// 2. 비동기
console.log(1);
setTimeout(() => {
console.log(2);
}, 3000);
console.log(3);
동기 코드: 순서대로 1을 출력한 뒤 바로 2를 출력한다.
비동기 코드: 1을 출력한 후 setTimeout을 만난다. 하지만 자바스크립트는 3초를 기다리지 않고 바로 다음 줄인 3을 출력한다. 그 후 3초가 지나면 미리 맡겨두었던 콜백 함수가 실행되어 2가 출력된다.
자바스크립트는 싱글스레드인데 어떻게 여러개 동시에 처리를 할 수 있을까??
자바스크립트 엔진은 싱글스레드이지만, 브라우저가 제공하는 Web APIs라는 조력자가 있기 때문에 여러 작업을 동시에 처리하는 것처럼 보일 수 있다.

자바스크립트 엔진: 코드를 한 줄씩 읽다가 setTimeout 같은 비동기 함수를 만나면, 직접 처리하지 않고 Web APIs에게 작업을 넘겨준다.
Web APIs (브라우저): 자바스크립트 엔진 대신 타이머를 작동시킨다. 그동안 엔진은 멈추지 않고 다음 줄에 있는 console.log("3")을 즉시 실행한다.
콜백 함수 전달: 타이머가 종료되면 Web APIs는 실행해야 할 함수(콜백 함수)를 태스크 큐라는 곳으로 보낸다.
최종 실행: 자바스크립트 엔진은 현재 수행 중인 모든 작업이 끝나서 'Call Stack'이 비게 되면, 그제야 큐에 대기 중인 콜백 함수를 가져와서 2를 출력한다.
비동기 작업 처리하기 1. 콜백함수
비동기 함수는 실행 결과가 즉시 나오지 않고 나중에 처리되기 때문에, 그 결과값을 함수 외부에서 사용하려면 특별한 방법이 필요하다. 이때 가장 기본적으로 사용되는 방식이 바로 **콜백 함수(Callback Function)**다.
1. 비동기 작업의 한계와 콜백의 필요성
단순히 비동기 함수 내부에서 값을 계산하고 출력하는 것만으로는 해당 데이터를 다른 로직에 전달하기 어렵다.
// 1.1 단순 출력 예제
function task() {
setTimeout(() => {
console.log("안녕하세요.");
}, 3000);
}
task();
// 1.2 비동기 계산 예제
function add(a, b) {
setTimeout(() => {
const sum = a + b;
console.log(sum); // 내부에서만 출력하고 종료됨
}, 3000);
}
add(1, 2);
콜백 함수를 통한 결과 전달
함수가 호출될 때 인자로 함수(콜백)를 전달하면, 비동기 처리가 끝난 시점에 그 결과값을 인자로 담아 호출해 줄 수 있다.
function add2(a, b, callback) {
setTimeout(() => {
const sum = a + b;
callback(sum); // 비동기 작업이 끝나면 콜백 함수에 결과 전달
}, 3000);
}
add2(1, 2, (value) => {
console.log(value); // 결과값인 3을 외부에서 사용 가능
});
2. 연속적인 비동기처리
음식을 주문하고, 식히고, 냉동하는 연속적인 과정을 비동기로 처리하는 상황을 가정해 보자.
// 음식 주문 (3초 소요)
function orderFood(callback) {
setTimeout(() => {
const food = "허니콤보";
callback(food);
}, 3000);
}
// 음식 식히기 (2초 소요)
function cooldownFood(food, callback) {
setTimeout(() => {
const cooldownedFood = `식은 ${food}`;
callback(cooldownedFood);
}, 2000);
}
// 음식 냉동하기
function freezeFood(food, callback) {
setTimeout(() => {
const freezedFood = `냉동된 ${food}`;
callback(freezedFood);
});
}
3. 콜백 지옥
위의 연속적인 과정을 실행하기 위해서는 콜백 함수 안에 또 다른 콜백 함수를 넣어야 한다.
orderFood((food) => {
console.log(food); // "허니콤보" 출력
cooldownFood(food, (cooldownedFood) => {
console.log(cooldownedFood); // "식은 허니콤보" 출력
freezeFood(cooldownedFood, (freezedFood) => {
console.log(freezedFood); // "냉동된 식은 허니콤보" 출력
});
});
});
정리
- 콜백 함수의 역할 비동기 작업이 언제 끝날지 모르는 상황에서, "작업이 끝나면 이 함수를 실행해 줘"라고 부탁하는 것과 같다.
비동기로 얻어낸 결과값(food, sum 등)을 다음 단계의 로직으로 안전하게 넘겨줄 수 있다.
- 콜백 지옥의 단점 예제처럼 작업이 3단계만 되어도 코드가 오른쪽으로 점점 밀려나며 들여쓰기가 깊어진다.
코드의 흐름을 한눈에 파악하기 어렵고, 에러 처리를 각각의 콜백마다 해줘야 하는 번거로움이 발생한다.
비동기 작업 처리하기 2. Promise
비동기 작업을 효율적으로 처리할 수 있도록 도와주는 자바스크립트의 내장 객체이다.

Promise의 3가지 상태

대기 (Pending): 작업이 완료되지 않은 초기 상태.
성공 (Fulfilled): 비동기 작업이 성공적으로 완료된 상태 (resolve 호출).
실패 (Rejected): 작업이 실패하거나 오류가 발생한 상태 (reject 호출).
1. Promise 생성과 실행자 (executor)
Promise 객체를 생성할 때 전달되는 함수를 '실행자(executor)'라고 부르며, 객체가 생성되는 즉시 자동으로 실행된다.
const promise = new Promise(() => {
// 비동기 작업을 실행하는 함수 (executor)
setTimeout(() => {
console.log("안녕");
}, 2000);
});
console.log(promise); // 2초 전에는 <pending> 상태 출력
2. 상태 변화와 결과 전달 (Resolve, Reject)
비동기 작업이 성공하면 resolve를, 실패하면 reject를 호출하여 상태를 변경하고 결과값을 전달한다.
성공 상황 (resolve)
const promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log("안녕");
resolve(); // 상태를 fulfilled로 변경
}, 2000);
});
setTimeout(() => {
console.log(promise2); // 3초 뒤 확인 시 <fulfilled> 상태
}, 3000);
// 결과값(데이터)을 함께 전달하는 경우
const promise3 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log("안녕");
resolve("안녕"); // 결과값 "안녕"을 함께 전달
}, 2000);
});
setTimeout(() => {
console.log(promise3); // 데이터 "안녕"이 담긴 상태
}, 3000);
실패 상황 (reject)
const promise4 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log("안녕");
reject("왜 실패했는지 이유..."); // 상태를 rejected로 변경하고 에러 메시지 전달
}, 2000);
});
setTimeout(() => {
console.log(promise4); // <rejected> 상태와 에러 메시지 출력
}, 3000);
3. 조건부 비동기 처리
실행자 내부 로직에 따라 성공과 실패를 나누어 처리할 수 있다.
const promise5 = new Promise((resolve, reject) => {
setTimeout(() => {
const num = 10;
if (typeof num === "number") {
resolve(num + 10); // 숫자라면 성공
} else {
reject("num이 숫자가 아닙니다."); // 아니라면 실패
}
}, 2000);
});
4. then/catch 메서드
Promise를 통해 처리된 결과는 then과 catch를 사용하여 받아온다.
// then: 성공 시 실행 / catch: 실패 시 실행
promise5
.then((value) => {
console.log(value); // resolve에서 보낸 20 출력
})
.catch((error) => {
console.log(error); // reject에서 보낸 에러 메시지 출력
});
5. Promise 체이닝 (Chaining)
Promise를 반환하는 함수를 이용하면 여러 비동기 작업을 줄지어 처리할 수 있다. 이를 통해 콜백 지옥을 방지한다.
function add10(num) {
const promise6 = new Promise((resolve, reject) => {
setTimeout(() => {
if (typeof num === "number") {
resolve(num + 10);
} else {
reject("num이 숫자가 아닙니다.");
}
}, 2000);
});
return promise6; // 생성된 Promise 객체를 반환
}
add10(0)
.then((result) => {
console.log(result); // 10
return add10(result); // 다시 Promise 반환 (체이닝)
})
.then((result) => {
console.log(result); // 20
return add10(undefined); // 고의로 실패 유도
})
.then((result) => {
console.log(result); // 앞선 단계가 실패하면 실행되지 않음
return add10(result);
})
.catch((error) => {
// 체이닝 도중 발생한 에러를 통합 관리
console.log(error); // "num이 숫자가 아닙니다."
});
정리
- Resolve와 Reject의 역할 Resolve: 비동기 작업이 끝났음을 알리고 결과 데이터를 넘겨주는 스위치 역할을 한다.
Reject: 예기치 못한 에러가 발생했을 때 이를 알리는 역할을 한다.
- then과 catch의 가독성 콜백 함수 방식은 작업이 많아질수록 코드가 오른쪽으로 깊어졌지만, Promise 체이닝은 아래로 길게 나열되기 때문에 데이터의 흐름을 파악하기 훨씬 쉽다.
catch 메서드를 마지막에 한 번만 써주면 중간에 어디서 에러가 나든 모두 잡아낼 수 있다는 큰 장점이 있다.
비동기 작업 처리하기 3. Async/Await
async와 await는 프로미스 객체를 기반으로 동작하며, 비동기 작업을 마치 동기 작업을 작성하는 것과 같은 가독성을 제공하는 키워드다.
1. async 키워드
어떤 함수를 비동기 함수로 만들어주는 키워드다. 이 키워드가 붙은 함수는 결과값으로 무엇을 반환하든 항상 프로미스 객체를 반환하게 된다.
// 1.1 일반 객체를 반환하는 async 함수
async function getData() {
return {
name: "이규현",
id: "kyulee",
};
}
// 호출 결과는 객체가 아닌, 성공(fulfilled) 상태의 프로미스 객체다.
console.log(getData());
// 1.2 명시적으로 프로미스를 반환하는 async 함수
async function getData2() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
name: "이규현",
id: "kyulee",
});
}, 1500);
});
}
console.log(getData2());
함수 내부에서 직접 프로미스를 생성하지 않더라도, async가 붙으면 반환값을 resolve하는 프로미스로 감싸서 내보낸다.
2. await 키워드
async 함수 내부에서만 사용할 수 있는 키워드로, 비동기 작업이 완료될 때까지 기다리는 역할을 한다.
// 2.1 기존 .then() 방식
function printData() {
getData2().then((result) => {
console.log(result); // 비동기 처리가 끝나면 결과 출력
});
}
printData();
// 2.2 await 방식
async function printData2() {
// getData2()가 완료되어 결과값을 던져줄 때까지 다음 줄로 넘어가지 않고 기다린다.
const data = await getData2();
// 비동기 작업의 결과값을 일반 변수에 담아 동기 코드처럼 편하게 사용한다.
console.log(data);
}
printData2();
await를 사용하면 비동기 함수의 처리 결과를 기다렸다가 변수에 바로 할당할 수 있다. 이는 .then() 체이닝보다 코드의 흐름을 파악하기 훨씬 쉽게 만든다.
await는 반드시 async 키워드가 붙은 함수 내부에서만 사용 가능하다.