JavaScript에서 배열을 다룰 때 더 이상 for 루프를 직접 작성해야 하는 경우는 드뭅니다. 이제 언어는 중첩되고 복잡한 데이터를 깔끔하고 읽기 쉬운 변환 과정으로 바꿔주는 표현력 있는 도구들을 제공합니다. 이 가이드는 기초를 넘어, 데이터의 원본을 유지하는 평탄화(flattening), 축약(reducing), 쿼리(querying), 그리고 최신 불변(immutable) 패턴에 집중합니다.

flat()flatMap()을 이용한 중첩 구조 평탄화

실제 데이터는 중첩된 경우가 많습니다. API에서 레코드를 가져오거나, CSV를 파싱하거나, 설정 파일을 읽다 보면 배열 안에 배열이 있는 형태를 자주 마주하게 됩니다. flat()은 이러한 계층을 하나의 선형 리스트로 압축하는 역할을 합니다.

중첩된 배열에 flat()을 호출하면 기본적으로 한 단계 깊이까지 평탄화합니다:

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

원본 nested 배열은 그대로 유지됩니다. 중첩이 더 깊다면 flat(2)와 같이 깊이 인자를 전달하거나, 계층 수를 모를 때는 flat(Infinity)를 사용하세요. 직접 재귀 함수를 작성하는 것보다 훨씬 효율적입니다.

요소를 변환하면서 동시에 결과물을 평탄화해야 할 때는 flatMap()이 한 번의 과정으로 두 가지를 모두 수행합니다. 각 요소에 매핑 함수를 실행한 뒤, 반환된 배열들을 한 단계 깊이로 평탄화합니다. .map().flat()을 따로 호출해도 되지만, flatMap()은 중간 배열을 건너뛰므로 메모리를 절약하고 반복 횟수를 줄일 수 있습니다. 대표적인 사례는 문장 배열을 개별 단어로 나누는 것입니다:

const lines = ['hello world', 'foo bar'];
const words = lines.flatMap(line => line.split(' '));
// ['hello', 'world', 'foo', 'bar']

필터링과 매핑: 차이점 알기

filter()는 테스트를 통과한 항목들로만 구성된 새로운 배열을 생성합니다. 콜백 함수는 요소를 유지하려면 truthy 값을, 버리려면 falsy 값을 반환해야 합니다. 원본 배열을 절대 변이(mutate)시키지 않습니다.

const users = [
  { name: 'Ajay', active: true },
  { name: 'Bina', active: false }
];
const activeUsers = users.filter(u => u.active);

filter()map()은 둘 다 새로운 배열을 반환하고 콜백을 받기 때문에 혼동하기 쉽습니다. 차이점은 '계약(contract)'에 있습니다. map()동일한 길이의 새로운 배열을 반환합니다. 모든 요소를 변환하고 조건 검사를 수행하면, 모든 슬롯에 true 또는 false가 나타날 것입니다. map()은 항목을 버리지 않고 오직 변경만 하기 때문입니다. 반면 filter()는 테스트를 통과한 요소 포함하는, 잠재적으로 더 짧은 배열을 반환합니다. 데이터를 제거하고 싶다면 filter()를 사용하세요. 개수를 유지하면서 모든 데이터를 변환하고 싶다면 map()을 사용하세요.

reduce()를 이용한 배열 축약

map()filter()가 배열을 반환하는 반면, reduce()는 전체 리스트를 하나의 값으로 응축합니다. 왼쪽에서 오른쪽으로 이동하며 각 단계마다 누산기(accumulator)를 전달합니다.

const totals = [10, 20, 30];
const sum = totals.reduce((acc, current) => acc + current, 0);
// 60

두 번째 인자(위 예제의 0)는 초기 누산기 값입니다. 이를 생략하면 JavaScript는 첫 번째 요소를 시작점으로 사용하여 인덱스 1부터 반복을 시작합니다. 배열이 비어 있을 때 초기값 없이 reduce()를 호출하면 TypeError가 발생하므로 주의해야 합니다.

reduce()는 산술 연산만을 위한 것이 아닙니다. 빈도수 테이블 구축, 객체 병합, 최댓값 찾기, 또는 여러 변환 과정을 거치는 데이터 파이프라인 구축에도 사용할 수 있습니다. 매우 유연하기 때문에 콜백 함수를 작게 유지하고 변수 이름을 명확하게 짓는 것이 좋습니다. 복잡한 reduce() 블록은 원래 대체하려 했던 루프보다 읽기 어려워질 수 있습니다.

불리언 쿼리: every()some()

때로는 새로운 배열이 아니라 컬렉션에 대한 '예/아니오' 답변이 필요할 때가 있습니다. every()모든 요소가 조건을 만족할 때만 true를 반환합니다. 첫 번째 요소부터 조건을 만족하지 않으면 즉시 중단하고 false를 반환합니다.

const scores = [85, 92, 78];
const allPassed = scores.every(s => s >= 50);

some()은 그 반대입니다. 최소 하나의 요소가 테스트를 통과하면 true를 반환합니다. 일치하는 항목을 찾으면 즉시 실행을 중단(short-circuit)합니다.

const hasPerfectScore = scores.some(s => s === 100);

이러한 메서드들은 검증 작업을 위한 수동 루프를 대체합니다. 모든 양식 필드가 채워졌는지 확인하려면 every()를 사용하세요. 사용자 중 관리자 권한을 가진 사람이 있는지 확인하려면 some()을 사용하세요.

변이 없이 검사 및 업데이트하기

현대적인 JavaScript는 불변성(immutability)을 매우 중시합니다. 이러한 메서드들은 원본을 건드리지 않고 배열을 검사하거나 업데이트하는 데 도움을 줍니다.

keys()는 배열의 인덱스를 포함하는 이터레이터 객체를 반환합니다. entries()[index, value] 쌍의 이터레이터를 반환합니다. 이들은 깔끔한 구조 분해 할당(destructuring)을 사용하고 싶을 때 for...of 루프에서 빛을 발합니다:

const fruits = ['apple', 'banana'];
for (const [index, fruit] of fruits.entries()) {
  console.log(index, fruit);
}

with() is a newer addition to the language that replaces an element at a specific index and returns a brand-new array. It answers the same need as bracket assignment—arr[index] = value—except it leaves the source array untouched. In frameworks like React, where direct mutation breaks change detection, with() is the safer path:

const original = [10, 20, 30];
const updated = original.with(1, 99);
// original: [10, 20, 30]
// updated:  [10, 99, 30]

Spread and Rest: Two Directions of ...

The spread syntax and rest parameter share the same three dots, but they move data in opposite directions.

Use the spread operator to expand an array into individual elements. It is the cleanest way to merge arrays or clone them:

const front = [1, 2];
const back = [3, 4];
const merged = [...front, ...back];

const copy = [...merged];

You can also sprinkle in extra items easily: [0, ...front, 99, ...back].

The rest parameter gathers multiple elements into a single array. Inside a function signature, it collects whatever arguments remain:

function logFirst(first, ...rest) {
  console.log('First:', first);
  console.log('Rest:', rest);
}
logFirst('a', 'b', 'c');
// First: a
// Rest: ['b', 'c']

Together they handle the common task of accepting variadic input, processing it as an array, and then splatting it back out into function calls or new data structures.

The Real Takeaway

The goal of these methods is not to impress teammates with one-liners. It is to make your intent obvious. When another developer sees filter(), they know you are removing items. When they see reduce(), they know you are collapsing data. When they see with(), they know the original array is still intact somewhere. Mastering these tools means writing code that explains itself, and that saves far more time than any clever loop ever will.