Javascript

한방에 자바스크립트 배열 마스터하기(뽀개기)!

드리프트2 2024. 8. 3. 11:53

 

한 여름입니다! 🏖️

 

드디어 코딩 실력 좀 올려볼까요? 🤔

 

이번엔 자바스크립트에서 절대 빼놓을 수 없는 배열 뽀개기에 도전해봅시다! 💪

 

자주 쓰는 배열 조작법부터 최신 기술까지, 알차게 준비했으니까 🤓 이번 방학에 배열 마스터하고 코딩 고수로 레벨업! 🚀

 

이 글을 읽으면 뭐가 좋냐고요? 😉

 

맨날 쓰던 for문 말고, 더 힙한 배열 조작법 득템! 😎
면접 때 "배열 잘 다루세요?" 질문에도 자신있게 대답 가능! 👨‍💻👩‍💻
팀플 과제 할 때 센스있는 코딩으로 팀원들에게 인정받기! 😏


배열 만들기 101

배열은 여러 값을 한 묶음으로 다루는 데이터 구조입니다. 자바스크립트에서는 일종의 객체로 취급됩니다. 🤔

 

배열 만드는 방법은 여러가지가 있습니다. 다 같은 결과지만, 미묘한 차이가 있으니까 잘 봐두세요! 👀

// 셋 다 똑같은 배열이 만들어집니다!
new Array("Alice", "Bob");
Array("Alice", "Bob");
Array.of("Alice", "Bob");
["Alice", "Bob"]; // 👈 이게 제일 깔끔하고 많이 쓰는 방법입니다!

 

new Array() (또는 Array())랑 Array.of()는 인수로 숫자 하나만 넣었을 때 동작이 다릅니다.

 

new Array()는 그 길이만큼 빈 배열을 만들고, Array.of()는 그 숫자 하나만 들어있는 배열을 만듭니다.

new Array(3); // [빈 요소 × 3]  (길이 3)
Array.of(3);  // [3]           (길이 1)

 

배열에는 null이나 undefined도 아닌, 빈 슬롯이라는 게 존재합니다. 👻

  • new Array(3) 처럼 길이만 정해서 배열 만들 때
  • 배열 길이보다 큰 인덱스에 값 넣을 때
  • arr.length에 숫자 넣어서 배열 길이 늘릴 때
const arr = ["a", "b"];
arr[4] = "e"; // ["a", "b", 빈 요소 × 2, "e"]
arr.length = 7; // ["a", "b", 빈 요소 × 2, "e", 빈 요소 × 2]

 

빈 슬롯은 배열 메서드마다 다르게 처리되니까 조심! ⚠️ 어떤 메서드는 무시하고, 어떤 메서드는 undefined로 취급합니다. 🤯 웬만하면 빈 슬롯 안 생기게 코딩하는 게 좋습니다! 👍

 

Array.from()은 배열을 좀 더 유연하게 만들 수 있는 꿀팁입니다! 😉 문자열이나 NodeList 같은 걸 배열로 바꿔줍니다.

// 문자열 넣으면 한 글자씩 쪼개서 배열로 만들어줍니다!
Array.from('test'); // ["t", "e", "s", "t"]

// NodeList도 배열로 뿅! ✨
// (HTML에 `<input name="test">`가 두 개 있을 때)
Array.from(document.getElementsByName("test")); // [input, input]

// 길이만 있는 객체 넣으면 그 길이만큼 빈 배열 생성!
Array.from({ length: 3 }); // [undefined, undefined, undefined]

// 함수 넣어서 커스텀 배열 만들기! 🛠️
Array.from([1, 2, 3], (e, i) => {
  // e는 각 요소, i는 인덱스 (0부터 시작!)
  return e + i; 
}); // [1, 3, 5]

 

Array.fromAsync()는 최신 기술입니다! ⚡️

 

Promise가 들어있는 배열이나 AsyncGenerator 같은 비동기 데이터도 배열로 만들 수 있습니다. 😎

// 1초 뒤에 숫자 반환하는 함수
const wait1sec = async (num) => {
    await new Promise(resolve => setTimeout(resolve, 1000));
    return num;
}

await Array.fromAsync([
    wait1sec(1),
    wait1sec(2),
    wait1sec(3),
], (e, i) => {
    return e + i;
}); // (1초 후에) [1, 3, 5]

// 비동기 제너레이터 함수
async function* getAsyncGenerator() {
  yield await wait1sec(1);
  yield await wait1sec(2);
  yield await wait1sec(3);
}

// 비동기 제너레이터로 배열 만들기!
await Array.fromAsync(getAsyncGenerator()); // (3초 후에) [1, 2, 3]

 

배열로 루프 돌리기 🎠

 

기본적인 방법부터 배열 자체 기능까지, 다양한 방법이 있습니다!

const arr = ["a", "b", "c"];

// 전통적인 for문
for (let i = 0; i < arr.length; i++) {
  arr[i]; // 각 요소에 접근!
}

// for...in 문 (인덱스로 접근)
for (const i in arr) {
  i;       // "0", "1", "2" (문자열!)
  arr[i]; // 각 요소에 접근!
}

// for...of 문 (요소로 접근)
for (const e of arr) {
  e; // 각 요소에 접근!
}

// forEach 메서드
arr.forEach((e) => {
  e; // 각 요소에 접근!
});

 

for(;;)에서는 const 못 쓰지만, for(in)이랑 for(of)에서는 쓸 수 있습니다! 😉

 

for문의 continuearr.forEach()에서 return으로 대체 가능!

 

하지만 breakarr.forEach()에서 못 씁니다! 🙅‍♀️

const arr = ["a", "b", "c"];

for(const e of arr) {
  if (e === "a") continue; // 다음 요소로!
  if (e === "b") break;    // for문 탈출!
}

arr.forEach((e) => {
  if (e === "a") return; // 다음 요소로!
  // break는 못 써! 😭
});

 

배열 조작하기 🛠️

 

배열에는 다양한 조작 기능이 있습니다! 유용한 것들만 쏙쏙 골라봤습니다! 😉

 

요소 하나만 쏙! 뽑아내기

const arr = ["a", "b", "c", "d"];

// 전통적인 인덱스 접근 방식
arr[0]; // "a"
arr[-1]; // undefined
arr[arr.length - 1]; // "d" (마지막 요소!)

// at 메서드 (음수 인덱스 지원!)
arr.at(0);  // "a"
arr.at(-1); // "d" (마지막 요소!)

// find 메서드 (조건에 맞는 첫 번째 요소 찾기)
arr.find(e => e === "b" || e === "c"); // "b"
arr.find(e => e === "e"); // undefined

// shift 메서드 (첫 번째 요소 뽑아내기)
arr.shift(); // "a" 
arr; // ["b", "c", "d"] (배열 자체가 바뀜! ⚠️)

// pop 메서드 (마지막 요소 뽑아내기)
arr.pop(); // "d" 
arr; // ["b", "c"] (배열 자체가 바뀜! ⚠️)

 

마지막 요소 뽑아낼 땐 arr[arr.length - 1]보다 arr.at(-1)이 훨씬 간편합니다! 😎

 

arr.shift()arr.pop()은 배열 자체를 바꾼다는 거 잊지마세요! 😱

 

이런 메서드를 파괴적 메서드라고 부릅니다. 배열 조작할 때는 파괴적인지 아닌지 꼭 확인해야 합니다! 🧐

⚠️ 주의! arr.shift()arr.pop() 뿐만 아니라, 다른 파괴적 메서드들도 const로 선언된 배열을 바꿀 수 있습니다! 🤯

 

arr.find()는 복잡한 조건으로 요소 찾을 때 유용합니다!

 

여러 개가 조건에 맞으면 첫 번째 것만 반환합니다. 최근에는 arr.findLast()도 나왔는데, 얘는 마지막에 찾은 걸 반환합니다! 😉

const arr = ["a", "b", "c", "d"];
arr.findLast(e => e === "b" || e === "c"); // "c"
arr.findLast(e => e === "e"); // undefined

요소 여러 개 뽑아내기

const arr = ["a", "b", "c", "d"];

// slice 메서드 (범위 지정해서 뽑아내기)
arr.slice(1, 3);   // ["b", "c"]
arr.slice(-3, -1); // ["b", "c"] (뒤에서부터 셀 수도 있습니다!)

// filter 메서드 (조건에 맞는 요소들만 뽑아내기)
arr.filter(e => e === "b" || e === "c"); // ["b", "c"]
arr.filter(e => e === "e"); // []

 

arr.slice()는 범위를 직접 지정하고, arr.filter()는 조건으로 뽑아내는 게 다릅니다!

 

arr.slice()는 첫 번째 인수가 시작, 두 번째 인수가 끝입니다.

 

둘 다 0부터 시작하는 인덱스! 😉 근데 두 번째 인수로 지정한 끝점은 포함 안 됩니다!🙅‍♀️

 

음수 값 넣으면 arr.at()처럼 동작합니다! 예를 들어 -1은 마지막 요소입니다!

 

요소 찾기 🔍

지금까지 본 것도 "찾기"지만, 요소가 있는지, 어디 있는지 확인하는 메서드도 있습니다!

const arr = ["a", "b", "c", "d"];

// includes 메서드 (값이 있는지 확인)
arr.includes("c"); // true
arr.includes("e"); // false

// some 메서드 (조건에 맞는 요소가 있는지 확인)
arr.some(e => e === "c"); // true
arr.some(e => e === "e"); // false

// every 메서드 (모든 요소가 조건에 맞는지 확인)
arr.every(e => typeof e === "string"); // true
arr.every(e => e === "c"); // false

 

arr.includes()는 값이 있는지 확인하고, arr.some()은 조건으로 확인합니다!

 

arr.every()는 모든 요소가 조건에 맞아야 true를 반환합니다!

// arr[1]이랑 arr[2]가 둘 다 "o"입니다!
const arr = ["f", "o", "o", "d"];

// indexOf 메서드 (처음 찾은 요소의 인덱스)
arr.indexOf("o"); // 1
arr.indexOf("e"); // -1 (없으면 -1!)

// lastIndexOf 메서드 (마지막에 찾은 요소의 인덱스)
arr.lastIndexOf("o"); // 2
arr.lastIndexOf("e"); // -1 (없으면 -1!)

 

arr.indexOf는 찾은 값의 첫 번째 인덱스를 반환하고, 없으면 -1을 반환합니다! (주의! false가 아닙니다! 🙅‍♀️)

 

arr.lastIndexOf는 마지막에 찾은 값의 인덱스를 반환합니다!

 

요소 값으로 꽉꽉 채우기

const arr = [1, null, 3];
arr.fill(0); // [0, 0, 0]
Array(5).fill(0); // [0, 0, 0, 0, 0] (초기화할 때 편합니다!)

 

arr.fill()은 배열을 특정 값으로 채워줍니다! 배열 자체를 바꾸고, 바뀐 배열을 반환합니다!

 

arr.fill()은 두 번째, 세 번째 인수로 범위를 지정할 수도 있습니다! arr.slice()랑 비슷하게 동작합니다! 😉

const arr = [1, null, 3, 4, 5];
arr.fill(0, 1, 3); // [1, 0, 0, 4, 5]

배열 붙이기 🔗

const arr1 = ["a", "b"];
const arr2 = ["c", "d"];

// 스프레드 구문 (펼쳐서 붙이기!)
[...arr1, ...arr2]; // ["a", "b", "c", "d"]
[...arr1, "e"];     // ["a", "b", "e"]
["e", ...arr1];     // ["e", "a", "b"]

// concat 메서드 (붙이기!)
arr1.concat(arr2);  // ["a", "b", "c", "d"]
arr1.concat("e"); // ["a", "b", "e"]
arr1.concat(arr2, "e"); // ["a", "b", "c", "d", "e"] (여러 개 붙일 수 있습니다!)

// unshift 메서드 (앞에 추가!)
arr1.unshift("e", "f"); // 4 (새로운 길이 반환!)
arr1; // ["e", "f", "a", "b"]

// push 메서드 (뒤에 추가!)
arr1.push("g", "h"); // 6 (새로운 길이 반환!)
arr1; // ["e", "f", "a", "b", "g", "h"]

 

arr.concat()도 좋지만, 비배열 뒤에 배열 붙일 땐 스프레드 구문이 더 편합니다! 😉

 

arr.unshift()arr.push()는 배열 자체를 바꾼다는 거 잊지마세요! 😱

 

arr.shift()arr.pop()이랑 비슷하지만, 반환값은 바뀐 배열의 길이입니다!

 

그리고 여기 나온 메서드들은 인수를 여러 개 넣을 수 있어서, 사실상 무한대로 붙일 수 있습니다! ♾️

 

배열 정렬하기 🧹

오름차순 (작은 순서대로)

const arr1 = [3, 2, 4, 1];
const arr2 = [
  { name: "Alice",   salary: 200_000 }, // 단위: 달러
  { name: "Bob",     salary: 180_000 }, // 단위: 달러
  { name: "Charlie", salary: 250_000 }, // 단위: 달러
];

// sort 메서드 (오름차순 정렬)
arr1.sort(); // [1, 2, 3, 4]
arr1; // [1, 2, 3, 4] (배열 자체가 바뀜! ⚠️)
arr2.sort((a, b) => a.salary - b.salary);
// [
//   { name: "Bob",     salary: 180_000 },
//   { name: "Alice",   salary: 200_000 },
//   { name: "Charlie", salary: 250_000 },
// ] (조건 지정해서 정렬 가능!)
const arr3 = [3, 2, 4, 1];

// toSorted() 메서드 (오름차순 정렬, 비파괴적!)
arr3.toSorted(); // [1, 2, 3, 4]
arr3; // [3, 2, 4, 1] (원래 배열은 그대로!)

 

arr.sort()가 전통적인 방법이지만, 배열 자체를 바꾼다는 거 잊지마세요! 😱 반환값은 arr이랑 똑같습니다!

 

원래 배열은 그대로 두고 싶을 땐 최신 기술인 arr.toSorted()를 쓰세요! 😎

 

arr.sort()랑 똑같이 쓰면 되는데, 원래 배열은 안 바꾸고 정렬된 새 배열을 반환합니다!

 

이렇게 원래 배열 안 바꾸는 메서드를 비파괴적 메서드라고 부릅니다.

 

최근에 배열 조작에 비파괴적 메서드가 많이 추가되었습니다! 😉 뒤에 나오는 arr.to***() 메서드들도 그 중 하나입니다!

 

arr.sort()arr.toSorted() 둘 다 인수로 정렬 조건을 나타내는 함수를 넣을 수 있습니다!

 

객체 정렬할 때 유용합니다! 😉 위에 예시가 있지만, 간단히 설명하면 이런 함수를 만들어야 합니다!

  • 비교할 두 개의 인수를 가진 함수 (a랑 b)
  • 다음 숫자 중 하나를 반환:
    • a가 더 크면 양수
    • a가 더 작으면 음수
    • 둘이 같으면 0

내림차순 (큰 순서대로)

const arr1 = [3, 2, 4, 1];

// reverse 메서드 (내림차순 정렬)
arr1.reverse(); // [1, 4, 2, 3] 
arr1; // [1, 4, 2, 3] (배열 자체가 바뀜! ⚠️)

const arr2 = [3, 2, 4, 1];

// toReversed 메서드 (내림차순 정렬, 비파괴적!)
arr2.toReversed(); // [1, 4, 2, 3]
arr2; // [3, 2, 4, 1] (원래 배열은 그대로!)

 

reverse() 메서드도 전통적인 방법이지만, 배열 자체를 바꾼다는 거 잊지마세요! 😱

 

반환값은 arr이랑 똑같습니다!

 

원래 배열은 그대로 두고 싶을 땐 최신 기술인 arr.toReversed()를 쓰세요! 😎

 

arr.toSorted()처럼 원래 배열은 안 바꾸고 정렬된 새 배열을 반환합니다!

 

그리고 sort() (또는 toSorted())랑 reverse() (또는 toReversed())를 조합하면 내림차순으로 정렬할 수 있습니다!

const arr = [3, 2, 4, 1];
arr.toSorted().toReversed(); // [4, 3, 2, 1]

// 숫자 배열이면 이렇게도 가능합니다!
arr.toSorted((a, b) => b - a); // [4, 3, 2, 1]

 

유연하게 가공하기 🎨

const arr1 = [3, 2, 4, 1];

// map 메서드 (각 요소를 함수 결과로 변경)
arr1.map(e => e * 2); // [6, 4, 8, 2]

// splice 메서드 (요소 제거/대체)
arr1.splice(1, 2, "a", "b"); // [2, 4] (제거된 요소 배열)
arr1; // [3, "a", "b", 1] (배열 자체가 바뀜! ⚠️)

const arr2 = [3, 2, 4, 1];

// toSpliced 메서드 (요소 제거/대체, 비파괴적!)
arr2.toSpliced(1, 2, "a", "b"); // [3, "a", "b", 1] (가공된 배열)
arr2; // [3, 2, 4, 1] (원래 배열은 그대로!)

 

arr.map()은 각 요소를 함수 결과로 바꾼 새 배열을 반환합니다! 모든 요소를 원하는대로 바꿀 수 있어서 엄청 유연합니다! 😎

 

arr.splice()는 요소를 제거하거나 대체할 수 있습니다!

 

첫 번째 인수는 시작 위치, 두 번째 인수는 제거할 개수, 세 번째 인수부터는 새로 넣을 값들입니다!

 

첫 번째 인수에 음수 넣으면 arr.at()처럼 동작합니다! 예를 들어 -1은 마지막 요소입니다!

 

반환값은 제거된 요소 배열이고, arr 자체가 바뀐다는 거 잊지마세요! 😱

 

최근에 나온 arr.toSpliced()는 가공된 배열을 반환하고, arr 자체는 안 바꿉니다! 😉

 

중첩 배열 펴기

const arr1 = [
  [10, 11, 12],
  [20, 21],
  [30],
];
const arr2 = [
  [[10, 11], 12], // 더 깊은 중첩!
  [20, 21],
  [30],
];

// flat 메서드 (중첩 배열 펴기)
arr1.flat();  // [10, 11, 12, 20, 21, 30]
arr2.flat();  // [[10, 11], 12, 20, 21, 30] (한 번만 펴짐)
arr2.flat(2); // [10, 11, 12, 20, 21, 30] (인수 넣으면 더 깊이 펴짐!)
const arr3 = [1, 2, 3];

// flatMap 메서드 (map 후 flat)
arr3.flatMap(e => e > 1 ? [e, 0] : 9); // [9, 2, 0, 3, 0]

// flatMap은 이렇게 쓸 수도 있습니다!
arr3.map(e => e > 1 ? [e, 0] : 9).flat(); // [9, 2, 0, 3, 0]

 

arr.flat()은 중첩 배열을 펴줍니다!

 

인수로 넣은 숫자만큼 펴고, 안 넣으면 한 번만 폅니다! 중첩이 두 단계 이상이면, 두 단계 이후는 그대로 중첩된 상태로 남습니다!

 

arr.flatMap()arr.map().flat()이랑 똑같습니다! 먼저 arr.map()하고 그 결과를 flat()하는 겁니다!

 

arr에 중첩이 없다는 걸 알면, 조건에 따라 빈 배열 반환하는 함수를 flatMap()에 넣어서 arr.filter()처럼 쓸 수도 있습니다! 😉

 

TypeScript에서 arr.filter() 때문에 타입 에러 났을 때 유용했습니다!

const arr = [1, null, 3];
arr.flatMap(e => {
    if (e === null) return []; // 제거하고 싶은 요소는 빈 배열 반환!
    return e * 2;
}); // [2, 6] (필터링 + 가공 동시에!)

 

배열을 하나의 값으로 뭉치기!

const arr1 = [1, 2, 3, 4];
const arr2 = ["a", null, "c", "d"];

// reduce 메서드 (배열을 하나의 값으로 축약)
arr1.reduce((prev, cur) => prev + cur, 0); // 10
arr2.reduce((prev, cur) => prev + "-" + cur); // "a-null-c-d"

// join 메서드 (배열을 문자열로 합치기)
arr2.join("-"); // "a--c-d"

 

arr.reduce()는 처음 두 요소를 함수에 넣고, 그 결과랑 세 번째 요소를 다시 함수에 넣고... 이걸 모든 요소에 대해 반복해서 하나의 값을 만들어냅니다!

 

위에 arr1 예시는 모든 요소의 합을 구하는 것입니다! 두 번째 인수에 초기값을 넣을 수 있습니다!

 

그러면 처음에 초기값이랑 첫 번째 요소가 함수에 들어갑니다!

 

arr.join()arr.reduce()의 특별 버전입니다! 배열을 문자열로 합치는 데 특화되어 있습니다!

 

인수로 구분 문자를 넣을 수 있습니다! 그리고 arr.join()null이나 undefined를 빈 문자열("")로 처리합니다!

 

참고로, arr.reduce() 말고 arr.reduceRight()도 있습니다! 얘는 arr.reduce()랑 똑같은데, 뒤에서부터 함수를 적용합니다!

const arr = ["a", "b", "c"];
arr.reduceRight((prev, cur) => prev + "-" + cur); // "c-b-a"

 

중복 제거하기 🚫

const arr = ["a", "i", "o", "i"];
Array.from(new Set(arr)); // ["a", "i", "o"]

 

배열에는 중복 제거하는 메서드가 없지만, Set 객체를 쓰면 가능합니다!

 

Set은 유일한 값만 저장하는 객체입니다! 중복을 허용하지 않습니다!

 

배열로 Set을 만들면 중복이 자동으로 제거됩니다! 그리고 그 Set으로 다시 배열을 만들면 중복 없는 배열 완성! ✨

 

문자열이나 숫자 같은 기본형 배열에서는 중복이 잘 제거되지만, 객체 배열에서는 내부 데이터 때문에 중복이 안 제거될 수도 있습니다! ⚠️

const arr = [
  { price: 100 },
  { price: 200 },
  { price: 100 },
];
Array.from(new Set(arr));
// [
//   { price: 100 },
//   { price: 200 },
//   { price: 100 },
// ] (내부 값은 중복 제거 안 됨! 🙅‍♀️)

마무리 🎉

이번 여름방학에 꼭 알아야 할 자바스크립트 배열 조작법을 알아봤습니다! 😎

 

배열은 프로그래밍에서 정말 중요합니다! 배열을 잘 다룰 수 있으면 어떤 상황에서든 유용하게 쓸 수 있을 겁니다! 😉

 

오늘 배운 내용들을 직접 써보고, 깔끔하고 멋진 코드를 작성해보세요! 👍