[JavaScript] 배열(Array) – 무조건 이해되는 자바스크립트

주요 포인트 한눈에 보기

배열(Array)은 장바구니, 검색 결과, 태그 목록처럼 “화면에 반복 렌더링되는 목록”의 출발점입니다. 이 문서는 배열을 만들고(생성), 원본을 지키고(복사), 필요한 것만 골라 쓰는(메서드) 흐름을 예제로 빠르게 정리합니다. reduce()는 활용 패턴이 많아 별도 글로 분리했으니, 아래 링크 섹션을 참고해 주세요.

배열 개념과 특징

배열은 “순서가 있는 목록”입니다. 인덱스와 length만 익히면, 대부분의 배열 코드를 읽기 시작할 수 있습니다.

const cartItems = ['apple', 'banana', 'peach'];

console.log(cartItems[0]); // 'apple' (첫 번째)
console.log(cartItems[cartItems.length - 1]); // 'peach' (마지막)
console.log(cartItems.length); // 3 (총 개수)

동작 설명

배열은 “번호가 붙은 목록”입니다. cartItems[0]처럼 번호(인덱스)로 바로 꺼낼 수 있고, length로는 “몇 개가 들어있는지”를 확인합니다.

초보자를 위한 포인트!

  • 인덱스는 0부터 시작합니다. (0번째가 첫 번째)
  • 마지막 값은 arr[arr.length - 1]로 자주 꺼냅니다.
  • 배열은 참조 타입이라 const b = a는 복사가 아닙니다. (복사는 아래 섹션)

배열 생성·복사·구조 분해

이 파트는 어렵게 외우지 않으셔도 됩니다. “만들기 → 복사하기 → 꺼내기”만 예시로 바로 확인하시면 됩니다.

배열 만들기

const arr = [10, 20, 30];
console.log(arr);

[]로 만들면 끝입니다. 값이 순서대로 들어가고, 그 순서대로 꺼내 쓰는 “목록”이 됩니다.

배열 복사 (원본 보호)

const origin = [1, 2, 3];
const alias = origin;      // 복사 아님(같은 배열 공유)
const copy = [...origin];  // 새 배열(복사)

alias.push(4);
console.log(origin); // [1, 2, 3, 4]
console.log(copy);   // [1, 2, 3]

alias = origin은 “복사”가 아니라 “같은 배열을 같이 보는 것”입니다. 원본을 지키려면 [...origin]처럼 새 배열을 만들어 작업하시면 됩니다.

객체가 들어있을 때 주의

const users = [{ id: 1 }, { id: 2 }];
const copied = [...users];

copied[0].id = 999;
console.log(users[0].id); // 999

배열은 새로 만들었지만, 안의 객체는 공유될 수 있습니다. 그래서 복사본에서 객체 속성을 바꾸면 원본 쪽에도 영향이 갈 수 있습니다.

Array.from으로 배열로 변환

function demo() {
  const realArray = Array.from(arguments);
  return realArray.map((v) => String(v));
}

console.log(demo(1, 2, 3)); // ['1','2','3']

arguments처럼 배열처럼 보이지만 배열 메서드 사용이 애매한 값이 있습니다. 이럴 땐 Array.from()으로 배열로 바꾼 다음 쓰시면 됩니다.

구조 분해로 값 꺼내기

const point = [10, 20];
const [x, y] = point;

console.log(x); // 10
console.log(y); // 20

인덱스를 여러 번 쓰지 않고도, 필요한 값만 변수로 바로 꺼낼 수 있습니다.

추가·삭제·수정: 원본 변경 메서드

이 파트의 메서드는 배열 “원본”을 직접 바꿉니다. 빠르고 편하지만, 같은 배열을 여러 곳에서 공유하면 예상치 못한 결과가 생길 수 있어 주의가 필요합니다.

push / pop (끝에 추가·삭제)

const cart = ['apple'];
cart.push('banana');
cart.pop();

console.log(cart); // ['apple']

이 메서드들은 “원본 배열을 직접 바꿉니다”. 로컬 임시 배열을 만들 때는 편하지만, 공유 데이터에는 조심하셔야 합니다.

splice (중간 삭제/삽입)

const list = ['a', 'b', 'c', 'd'];
list.splice(1, 2, 'x');

console.log(list); // ['a', 'x', 'd']

splice는 중간을 잘라내거나 바꾸는 데 강력하지만, 원본이 바뀌기 때문에 “원본 유지가 필요한 곳”에서는 주의가 필요합니다.

sort (원본 변경) + 안전한 정렬

const scores = [3, 1, 2];

const sorted1 = [...scores].sort((a, b) => a - b);
console.log(scores);  // [3, 1, 2]
console.log(sorted1); // [1, 2, 3]

sort()는 원본을 바꿉니다. 그래서 안전하게 정렬하려면 [...scores]로 복사 후 정렬하는 방식이 가장 흔합니다.

배열에서 “찾기/확인하기”를 담당하는 메서드들입니다. 무엇이 있는지 확인하거나, 조건을 만족하는 값을 찾고, 필요하면 중간에 멈추는 패턴까지 함께 익힙니다.

includes / indexOf (있나? 어디 있나?)

const tags = ['js', 'ts', 'react'];

console.log(tags.includes('ts')); // true
console.log(tags.indexOf('react')); // 2
console.log(tags.indexOf('next'));  // -1

includes는 “있는지”만 필요할 때 가장 깔끔합니다. indexOf는 “몇 번째인지”가 필요할 때 씁니다. 없으면 -1이 나옵니다.

find / findIndex (첫 번째로 찾기)

const users = [
  { id: 1, name: 'A' },
  { id: 2, name: 'B' },
];

const u = users.find((x) => x.id === 2);
const idx = users.findIndex((x) => x.id === 2);

console.log(u);   // { id: 2, name: 'B' }
console.log(idx); // 1

find는 “요소 자체”를, findIndex는 “위치(인덱스)”를 반환합니다. 찾자마자 멈추기 때문에 “첫 번째만 필요할 때” 좋습니다.

forEach는 중단이 안 됩니다

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

nums.forEach((n) => {
  if (n === 3) return; // 반복 중단이 아님 (그냥 이번 콜백만 종료)
  console.log(n);
});

forEach는 중간에 멈출 수 없습니다. “찾으면 멈추기”가 필요하면 아래처럼 some 또는 find를 쓰는 편이 안전합니다.

some으로 “찾으면 멈추기”

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

nums.some((n) => {
  if (n === 3) return true; // true면 즉시 중단
  console.log(n);
  return false;
});

some은 콜백이 true를 반환하는 순간 즉시 멈춥니다. “조건을 만족하는 게 하나라도 있나요?” 같은 질문에 특히 잘 맞습니다.

변환·가공 메서드

원본은 그대로 두고, 새 배열을 만들어 “모양을 바꾸거나(map)” “필요한 것만 남기는(filter)” 작업을 합니다. 화면에 뿌릴 데이터를 만들 때 가장 자주 쓰입니다.

map / filter (변환 vs 추출)

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

const doubled = nums.map((n) => n * 2);
const evens = nums.filter((n) => n % 2 === 0);

console.log(doubled); // [2, 4, 6, 8]
console.log(evens);   // [2, 4]

map은 “모양 바꾸기(변환)”, filter는 “조건에 맞는 것만 남기기(추출)”입니다. 둘 다 새 배열을 만들어 원본은 그대로 둡니다.

flat / flatMap (중첩을 펴기)

const nested = [[1, 2], [3, 4]];
console.log(nested.flat()); // [1, 2, 3, 4]

const words = ['hi', 'ok'];
console.log(words.flatMap((w) => w.split(''))); // ['h','i','o','k']

flat은 중첩 배열을 펴고, flatMap은 “map + flat”을 한 번에 처리합니다.

오류 예제: map에서 push 쓰기

const nums = [1, 2, 3];
const out = [];

nums.map((n) => {
  out.push(n * 2);
});

console.log(out); // [2, 4, 6]

map은 “반환값으로 새 배열을 만드는 도구”인데, 위처럼 외부 배열에 push하면 의도가 흐려집니다.

개선 예제: map은 반환으로 끝내기

const nums = [1, 2, 3];
const out = nums.map((n) => n * 2);

console.log(out); // [2, 4, 6]

같은 결과라도 “의도가 보이게” 쓰면 코드가 훨씬 읽기 쉬워집니다.

정렬·역순·부분 변경: 비파괴 메서드

정렬·역순·부분 교체가 필요해도, 원본을 지키고 새 결과를 얻는 방법입니다. 상태 관리나 렌더링 데이터처럼 “원본 보호”가 중요한 곳에서 특히 유용합니다.

toSorted / toReversed (원본 유지)

const a = [3, 1, 2];
const sorted = a.toSorted((x, y) => x - y);
const reversed = a.toReversed();

console.log(a);        // [3, 1, 2]
console.log(sorted);   // [1, 2, 3]
console.log(reversed); // [2, 1, 3]

이 메서드들은 원본을 바꾸지 않고, 결과를 새 배열로 반환합니다. “원본 유지가 중요한 코드”에서 특히 안전합니다. 구형 환경을 고려해야 한다면 [...a].sort() 같은 방식으로 대체하는 편이 무난합니다.

toSpliced / with (부분 수정도 안전하게)

const items = ['a', 'b', 'c', 'd'];

const removed = items.toSpliced(1, 2); // 1번부터 2개 제거한 새 배열
const replaced = items.with(1, 'x');   // 1번 값을 바꾼 새 배열

console.log(items);    // ['a', 'b', 'c', 'd']
console.log(removed);  // ['a', 'd']
console.log(replaced); // ['a', 'x', 'c', 'd']

중간 삭제/교체가 필요해도 원본을 보존할 수 있습니다. splice와 달리 “원본을 지키는” 점이 핵심입니다.

배열 메서드 정리표 (2행)

모바일에서 깨지지 않도록 2행으로만 정리했습니다. 원본을 바꾸는 메서드와, 원본을 유지하는 메서드를 빠르게 구분하는 용도입니다.

구분 대표 메서드 한 줄 요약
원본 변경(주의) push, pop, splice, sort, reverse 배열 자체가 바뀜 (공유 데이터면 버그 원인)
비변경/비파괴(추천) map, filter, slice, concat, toSorted, toReversed, toSpliced, with 원본 유지 + 새 결과를 반환 (상태/렌더링에 안전)
JavaScript reduce 함수를 사용해 배열의 숫자를 합산하는 코드를 타이핑하는 개발자 이미지

reduce()는 활용 패턴이 많아서 따로 자세히 정리해두었습니다. 필요하실 때 아래 링크를 참고해 주세요.

[JavaScript] reduce – 무조건 이해되는 자바스크립트 (전체 가이드)

결론

배열은 결국 “목록을 화면에 쓰기 좋은 형태로 만들기” 위한 도구입니다. 그래서 실무에서는 문법을 외우기보다, 어떤 메서드를 쓰면 의도가 잘 보이는지를 기준으로 선택하시는 편이 훨씬 빠르게 늘어납니다.

  • 찾기/확인: 하나만 찾으면 find, 여러 개면 filter, 존재 여부면 includes/some을 우선 떠올립니다.
  • 가공: 값 모양을 바꾸면 map, 조건으로 걸러내면 filter를 기본으로 잡습니다.
  • 원본 변경 주의: splice, sort, reverse는 원본을 바꾸므로 공유 데이터에서는 특히 조심합니다.
  • 안전한 업데이트: 원본을 지키고 싶으면 복사 후 작업([...arr].sort())이나 비파괴 메서드(toSorted, toSpliced, with)를 고려합니다.

FAQ (면접 질문)

Q. map과 forEach의 차이와 선택 기준을 말해보세요.
map은 “새 배열을 만들 때” 쓰고, 콜백의 반환값이 새 배열의 요소가 됩니다. forEach는 “그냥 실행” 용도라 반환값이 의미 없고, 중간에 멈추기도 어렵습니다.

Q. find와 filter는 결과가 어떻게 다르며, 언제 각각 쓰나요?
find는 조건을 만족하는 “첫 번째 요소 1개”를 반환하고 못 찾으면 undefined입니다. filter는 조건을 만족하는 “전부”를 모아 “새 배열”로 반환합니다.

Q. sort나 splice가 실무에서 위험하다고 하는 이유는 무엇인가요?
둘 다 원본 배열을 바꿉니다. 같은 배열을 다른 화면/로직이 공유하고 있으면, 한 번의 정렬·삭제가 다른 곳 결과까지 바꾸면서 버그가 나기 쉽습니다. 그래서 보통 “복사 후 작업”을 합니다.

Q. includes와 indexOf는 어떤 차이가 있나요?
includes는 “있는지(불리언)”만 필요할 때 가장 읽기 쉽습니다. indexOf는 “몇 번째인지(숫자)”가 필요할 때 쓰고, 없으면 -1이 나옵니다.

Q. […arr]로 복사했는데도 원본이 바뀌는 것처럼 보일 때가 있습니다. 왜 그런가요?
배열 안에 객체가 들어있으면, 배열은 새로 만들었더라도 “안의 객체”는 공유될 수 있습니다. 그래서 복사본에서 객체 속성을 바꾸면 원본 쪽에도 영향이 갈 수 있습니다.

이 글이 마음에 드세요?

RSS 피드를 구독하세요!

댓글 남기기