배열 메서드


가격 목록이 있고, 각 가격에 세금을 더한 두 번째 목록을 만들고 싶다고 해봅시다. 배열을 순회하면서 합을 계산하고 결과를 새 배열에 추가하는 for 루프를 작성할 수도 있습니다. 이렇게 해도 되지만, "각 항목을 가져와서 변형한다"는 작은 아이디어치고는 준비할 게 너무 많습니다. 배열 메서드는 바로 이런 종류의 작업을 위해 내장된 도구입니다. 목록에 무엇을 하고 싶은지만 말하면 되고, 그것을 실행하는 루프를 일일이 풀어 쓰지 않아도 됩니다. 이 장에서는 매일같이 손이 가게 될 배열 메서드들을 다룹니다. 이 메서드들은 배열과 함수를 그대로 활용하므로, 두 내용을 가까이에 두고 보시길 권합니다.
map: 모든 항목을 변형하기
map은 **메서드**입니다. 배열에 속해 있으면서 그 배열에 어떤 작업을 하는 함수입니다. map은 여러분이 준 작은 함수에 각 항목을 통과시켜 그 결과들을 모아 새 배열을 만듭니다. 그 함수를 넘겨주면, map이 항목을 하나씩 그 함수에 건네줍니다.
가격 목록이 있고 각 값을 두 배로 만들고 싶다고 해봅시다.
const prices = [2, 4, 6];
const doubled = prices.map((price) => price * 2);
console.log(doubled); // [4, 8, 12](price) => price * 2 부분은 화살표 함수입니다. 항목 하나(price)를 받아서 그것이 무엇으로 바뀌어야 하는지(price * 2)를 반환하는 짧은 함수입니다. map은 이 함수를 항목마다 한 번씩 호출해서 그 결과를 같은 순서로 새 배열에 담습니다.
map은 목록을 받아서 같은 길이의 새 목록을 돌려주는데, 그 목록의 모든 항목은 여러분이 만든 작은 함수를 통과한 결과입니다. 화살표 함수가 각 항목이 무엇으로 바뀌는지를 말해주고, map은 그것을 모든 항목에 순서대로 적용합니다. 루프를 써서 두 번째 목록을 만들고 있다는 걸 깨달을 때마다 map을 떠올리세요. filter: 통과한 항목만 남기기
map이 모든 항목을 변형한다면, filter는 그중 일부만 남깁니다. 각 항목에 대해 예/아니오로 답하는 함수를 주면, filter는 "예"를 받은 항목들로만 새 배열을 만듭니다.
const scores = [40, 75, 90, 55, 88];
const passing = scores.filter((score) => score >= 60);
console.log(passing); // [75, 90, 88](score) => score >= 60 함수는 각 점수에 대해 true 또는 false를 반환합니다. filter는 true인 항목만 남기고 나머지는 버립니다. 새 배열은 원본보다 짧아질 수 있지만, 순서는 절대 바뀌지 않습니다.
filter는 테스트를 통과한 항목만 남기고 나머지는 버리므로 새 배열은 대개 더 짧아집니다. 여러분의 함수가 각 항목에 대해 예/아니오로 답하고, "예"라고 답한 것만 통과합니다. 순서를 절대 바꾸지 않으므로, 남은 항목들은 원래 순서를 그대로 유지합니다. reduce: 값 하나로 압축하기
map과 filter는 둘 다 배열을 돌려줍니다. reduce는 배열을 값 하나로 압축하는 메서드입니다. 합계, 최댓값, 이어붙인 문자열 같은 것들이죠. 다른 메서드보다 헷갈리기 쉬우니 천천히 살펴볼 만합니다.
reduce는 배열을 순회하면서 진행 중인 결과 값을 함께 가지고 다닙니다. 각 항목마다 "지금까지의 결과와 이 항목을 가지고, 새로운 결과는 무엇인가?"를 묻습니다.
const prices = [10, 20, 30];
const total = prices.reduce((sum, price) => sum + price, 0);
console.log(total); // 60이 함수는 두 가지를 받습니다. 지금까지의 합계인 sum과 현재 항목인 price입니다. 이 함수는 sum + price를 반환하고, 이 값이 다음 항목을 위한 새로운 합계가 됩니다. 마지막에 있는 0은 합계가 시작되는 값입니다. 그래서 0 + 10, 그다음 10 + 20, 그다음 30 + 30을 거쳐 60에 이릅니다.
reduce는 진행 중인 결과 값을 목록 전체에 걸쳐 가지고 다니면서 각 항목마다 갱신합니다. 콜백은 지금까지의 결과와 현재 항목을 받아서 새로운 결과를 반환합니다. 마지막 인자는 시작 값이고 보통 합계라면 0입니다. 저는 이걸 한 단계씩 소리 내어 읽어봤을 때 딱 이해가 됐습니다. find, some, every
이 세 메서드는 filter와 가까운 친척입니다. 배열에 테스트를 적용한다는 점은 같지만, 새 배열이 아니라 더 좁은 질문에 답합니다.
find는 테스트를 통과한 첫 항목 하나를 돌려줍니다. 통과한 항목 전체 목록이 아닙니다. some과 every는 배열 전체에 대한 참/거짓 질문에 답합니다.
const scores = [40, 75, 90, 55];
const firstPass = scores.find((score) => score >= 60);
console.log(firstPass); // 75
const anyPass = scores.some((score) => score >= 60);
console.log(anyPass); // true
const allPass = scores.every((score) => score >= 60);
console.log(allPass); // falsefind는 처음으로 조건에 맞는 항목(75)을 반환합니다. some은 "통과한 항목이 하나라도 있는가?"를 물어서, 적어도 하나가 통과했으므로 true를 반환합니다. every는 "전부 통과했는가?"를 물어서, 40과 55가 통과하지 못했으므로 false를 반환합니다.
find는 통과한 첫 항목 하나를 주지, 전체 목록을 주지 않습니다. some은 "하나라도 통과했나?"를 묻고 every는 "전부 통과했나?"를 묻는데, 둘 다 true나 false로 답합니다. 필터된 배열이 아니라 항목 하나나 예/아니오 답을 원할 때 이것들을 쓰세요. 체이닝과 가독성 유지하기
앞서 다룬 메서드들은 조합했을 때 이득이 되는 설계를 공유합니다. 각 메서드는 새로운 값을 반환하고 원본 배열은 그대로 둡니다.
map, filter, 그리고 나머지 메서드들은 처음에 가지고 있던 배열을 절대 바꾸지 않습니다. 새 배열(또는 값)을 돌려주고 원본은 그대로 둡니다.
const prices = [10, 20, 30];
const doubled = prices.map((price) => price * 2);
console.log(doubled); // [20, 40, 60]
console.log(prices); // [10, 20, 30]map과 filter 둘 다 배열을 돌려주기 때문에, 이 둘을 나란히 이어 쓸 수 있습니다. 먼저 필터링하고, 살아남은 항목들을 map으로 처리하는 식입니다.
const prices = [10, 20, 30, 40];
const bigDoubled = prices.filter((price) => price >= 20).map((price) => price * 2);
console.log(bigDoubled); // [40, 60, 80]filter와 map 둘 다 배열을 반환하기 때문에 체인으로 이어 쓸 수 있습니다. 먼저 필터링하고, 남은 것들을 map으로 처리하세요. 체인은 위에서 아래로 한 단계씩 읽으세요. 다음으로 가볼 곳
map, filter, reduce는 목록을 가지고 할 일 대부분을 다룹니다. 변형하거나, 좁히거나, 압축하는 것이죠. find, some, every는 더 좁은 질문에 답합니다. 이것들이 익숙해지면, 배열을 순회하던 대부분의 for 루프가 의도를 그대로 말해주는 한두 줄로 바뀝니다.
자연스러운 다음 단계는 객체입니다. 여기서는 배열이 단순한 숫자와 문자열보다 더 풍부한 항목들을 담게 되는데, 같은 메서드들이 그 항목들에도 그대로 동작합니다. 여기 나온 콜백 문법이 조금 불안하게 느껴졌다면, 함수를 다시 훑어보면 나머지가 훨씬 잘 이해될 것입니다.

