본문으로 건너뛰기

map·filter·find

자바스크립트 목차배열레슨 38 / 76

목차

filter·map·find는 배열의 값마다 우리가 넘긴 함수를 불러, 거른 새 배열·바꿔 담은 새 배열·처음 맞는 값 하나를 돌려주는 배열 메서드입니다. 원래 배열은 그대로 둡니다.

거르기와 바꿔 담기의 틀은 빈 배열을 만들고, 하나씩 꺼내고, 맞으면 담는 줄을 매번 다시 씁니다. 그런데 매번 달라지는 것은 조건이나 담을 값 한 줄뿐입니다. 이 레슨은 그 한 줄만 함수로 넘기는 메서드 filter·map·find·forEach를 차례로 봅니다.

10단계 중 1단계. 4000보다 큰 가격만 거르는 코드입니다. 조건은 filter에 넘긴 함수가 답합니다.

1filter — 함수가 true를 돌려준 칸만 새 배열로

그림의 코드에 찍는 두 줄을 더했습니다.

const prices = [900, 4500, 700];
const big = prices.filter(
  (p) => p > 4000,
);
console.log(big);
console.log(prices);
실행 결과
[ 4500 ]
[ 900, 4500, 700 ]

3번 줄의 (p) => p > 4000은 화살표 함수(화살표 함수와 함수 값)를 이름 없이 인자 자리에 바로 쓴 것입니다. filter가 이 함수를 받아 0번 칸부터 한 번씩 부릅니다(함수를 넘기기). 이름이 없어 그림은 부를 때 생기는 칸을 (익명)이라고 적습니다. 나눠 쓴 인자 끝의 쉼표는 있어도 없어도 같습니다. 함수가 true를 돌려준 칸의 값만 새 배열 ②에 담기고, prices의 ①은 그대로입니다.

앞 레슨의 거르기 틀과 견주면, 틀에서 직접 쓰던 줄을 filter가 맡습니다.

거르기 틀(for...of)filter
const picked = [];filter가 새 배열을 만든다
for (const price of prices)filter가 칸마다 함수를 부른다
if (price > 4000)함수가 true·false를 돌려준다
picked.push(price);true를 받은 칸의 값이 담긴다

2map — 함수가 돌려준 값을 담은 새 배열

map도 칸마다 함수를 부르지만, 함수가 돌려준 값을 그대로 담습니다. 앞 레슨의 500원 올리기를 map으로 쓰면 이렇습니다.

const prices = [900, 4500, 700];
const up = prices.map(
  (p) => p + 500,
);
console.log(up);
실행 결과
[ 1400, 5000, 1200 ]

map이 돌려준 새 배열은 칸 수가 원래 배열과 같습니다. filter는 칸 수가 줄 수 있지만 값은 바꾸지 않습니다.

3find — 처음 맞는 값 하나

find도 칸마다 함수를 부르지만, 돌려주는 것은 배열이 아닙니다. 같은 배열에서 두 번 찾습니다. 마지막 줄까지 무엇이 찍힐지 정한 뒤 펼쳐 보세요.

const prices = [900, 4500, 700];
const a = prices.find(
  (p) => p > 4000,
);
const b = prices.find(
  (p) => p > 9000,
);
console.log(a);
console.log(b);
실행 결과 보기
실행 결과
4500
undefined

예상과 다르면 그곳이 배울 곳입니다. a는 [ 4500 ]이 아니라 값 4500입니다. find는 함수가 처음 true를 돌려준 칸의 값 하나를 돌려줍니다. 9000보다 큰 값이 없어 b는 undefined입니다. 못 찾아도 오류는 나지 않고, 없는 칸을 읽을 때처럼 undefined가 돌아옵니다.

find는 true를 받으면 거기서 멈추고 뒤 칸은 부르지 않습니다. a를 찾을 때 700은 보지 않습니다.

4forEach — 돌려주는 값 없이 하나씩

새 값 없이 하나씩 무엇을 하기만 할 때는 forEach를 씁니다.

const today = ['커피', '라테'];
const r = today.forEach((o) => {
  console.log(o);
});
console.log(r);
실행 결과
커피
라테
undefined

forEach는 함수가 돌려준 값을 쓰지 않고, 자기도 undefined를 돌려줍니다.

네 메서드는 모두 칸마다 함수를 부르고, 돌려받은 값을 쓰는 법만 다릅니다. filter는 true인 칸을 담고, map은 받은 값을 담고, find는 처음 true에서 멈추고, forEach는 쓰지 않습니다.

예제

4000원 아래 메뉴의 가격표

filter가 돌려준 새 배열에 다시 map을 부릅니다.

const prices = [900, 4500, 700];
const cheap = prices.filter(
  (p) => p < 4000,
);
const labels = cheap.map(
  (p) => p + '원',
);
console.log(labels);
실행 결과
[ '900원', '700원' ]

대표 오류 — find가 돌려준 값에 map

find가 돌려준 것을 배열로 알고 map을 부르면 멈춥니다.

const orders = ['커피', '라테'];
const order = orders.find(
  (o) => o === '라테',
);
const loud = order.map(
  (o) => o + '!',
);
실행 결과
main.js:5const loud = order.map(                   ^ TypeError: order.map is not a function    at main.js:5:20

order는 배열이 아니라 글자 '라테'입니다. 글자에는 map이 없어 order.map은 undefined이고, 그것을 부르려다 멈춥니다. 고른 값들을 배열로 받으려면 filter를 씁니다.

과목 끝의 할 일 앱에서도 필터 단추는 할 일 배열을 filter로 거르고, 지우기는 지울 할 일만 뺀 filter 결과에 이름을 다시 붙입니다.

자주 틀리는 곳

틀린 생각: 함수 몸을 중괄호로 감싸도 식의 값이 돌아간다.

(p) => p + 500은 return 없이 식의 값을 돌려주니, 중괄호로 감싸도 같을 것 같습니다.

const prices = [900, 4500];
const up = prices.map((p) => {
  p + 500;
});
console.log(up);
실행 결과
[ undefined, undefined ]

3번 줄은 계산만 하고 값을 버립니다. 중괄호 몸은 return이 없으면 undefined를 돌려주고, map은 그것을 담습니다.

맞는 규칙: 중괄호 몸에는 return p + 500;처럼 return을 씁니다(화살표 함수와 함수 값).

틀린 생각: map은 원래 배열의 칸을 바꾼다.

값마다 새 값으로 바꾼다고 하니, 부르기만 해도 칸이 바뀔 것 같습니다.

const prices = [900, 4500];
prices.map(
  (p) => p + 500,
);
console.log(prices);
실행 결과
[ 900, 4500 ]

map은 새 배열을 돌려줄 뿐입니다. 2번 줄은 그 새 배열에 이름을 붙이지 않아 버렸습니다.

맞는 규칙: map·filter는 원래 배열을 그대로 두고 새 배열을 돌려줍니다. 결과는 const up = prices.map(…)⁠처럼 이름을 붙여 둡니다.

틀린 생각: 콜백 안에서 return하면 바깥 함수도 끝난다.

라테를 찾자마자 return true;를 하면 hasLatte가 거기서 끝날 것 같습니다.

function hasLatte(orders) {
  orders.forEach((o) => {
    if (o === '라테') {
      return true;
    }
  });
  return false;
}
const today = ['라테', '커피'];
console.log(hasLatte(today));
실행 결과
false

4번 줄의 return은 지금 부른 화살표 함수의 칸만 끝냅니다. forEach는 받은 true를 쓰지 않고 다음 칸 '커피'로 또 부릅니다. 다 돈 뒤 7번 줄이 false를 돌려줍니다.

맞는 규칙: return은 그 return이 든 함수의 칸만 끝냅니다(규칙 5). 찾으면 끝내려면 find를 쓰거나, forEach 대신 for...of 안에서 return합니다. for...of는 함수가 아니라서 그 return이 hasLatte의 칸을 끝냅니다.

틀린 생각: 넘길 함수에도 괄호를 붙인다.

함수는 괄호를 붙여야 불리니, 넘길 때도 붙여야 할 것 같습니다.

const prices = [900, 4500];
const add = (p) => p + 500;
const up = prices.map(add());
console.log(up);
실행 결과
main.js:3const up = prices.map(add());                  ^ TypeError: number NaN is not a function    at Array.map (<anonymous>)    at main.js:3:19

add()는 넘기기 전에 바로 불립니다. p가 undefined라 NaN이 돌아오고, map은 함수 대신 수 NaN을 받아 멈춥니다.

맞는 규칙: 함수를 넘길 때는 prices.map(add)처럼 이름만 씁니다. 부르는 일은 받은 map이 합니다(함수를 넘기기).

확인 문제

1. 무엇이 찍힐까요?

const cups = [12, 20, 9, 15];
const busy = cups.filter(
  (c) => c >= 15,
);
const two = busy.map(
  (c) => c * 2,
);
console.log(two);
console.log(cups.length);
힌트

busy가 붙은 배열부터 적고, map은 그 배열의 칸마다 부릅니다.

정답
실행 결과
[ 40, 30 ]
4

filter가 15 이상인 20과 15만 담은 새 배열을 돌려주고, map이 그 두 칸을 두 배로 바꿔 담습니다. cups는 처음 그대로라 칸이 넷입니다.

흔한 오답은 둘째 줄을 2로 본 것입니다. filter가 cups의 칸을 지운다고 본 것입니다(원래 배열이 바뀐다는 생각).

2. 반복과 메서드를 서로 바꿔 씁니다.

(가) 아래 반복과 같은 배열을 map으로 만들도록 빈칸을 채우세요.

const prices = [800, 4000];
const half = [];
for (const p of prices) {
  half.push(p / 2);
}
console.log(half);
실행 결과
[ 400, 2000 ]
const prices = [800, 4000];
const half = prices.map(
  ________
);
console.log(half);
힌트

반복에서 담는 값 p / 2가 함수가 돌려줄 값입니다.

정답

(p) => p / 2,입니다.

const prices = [800, 4000];
const half = prices.map(
  (p) => p / 2,
);
console.log(half);
실행 결과
[ 400, 2000 ]

흔한 오답은 (p) => half.push(p / 2),⁠입니다. map이 새 배열을 만들어 돌려주니 담는 일은 쓰지 않습니다. 이 코드는 const half가 아직 만들어지지 않은 때 half를 읽어 멈춥니다.

(나) 아래 filter와 같은 배열을 만드는 거르기 틀을 for...of로 쓰세요. [ 80, 90 ]이 찍혀야 합니다.

const scores = [80, 55, 90];
const passed = scores.filter(
  (s) => s >= 60,
);
console.log(passed);
힌트

빈 배열, 하나씩 꺼내 (맞으면) 담기, 끝나고 쓰기 순서입니다. 함수의 조건이 if 조건이 됩니다.

정답
const scores = [80, 55, 90];
const passed = [];
for (const s of scores) {
  if (s >= 60) {
    passed.push(s);
  }
}
console.log(passed);
실행 결과
[ 80, 90 ]

다르게 써도 되는 곳: 꺼낸 값의 이름은 score처럼 지어도 됩니다.

흔한 오답은 if 없이 바퀴마다 담는 것입니다. 그러면 거르지 않아 [ 80, 55, 90 ]이 찍힙니다. 함수가 true를 돌려준 칸만 담긴다는 것이 if 한 줄입니다.

3. 무엇이 찍힐까요?

const names = ['민지', '준호'];
const r = names.forEach((n) => {
  return n + '님';
});
console.log(r);
힌트

forEach가 함수가 돌려준 값을 어디에 쓰는지 4절에서 찾아봅니다.

정답
실행 결과
undefined

forEach는 칸마다 함수를 부르지만 돌려받은 '민지님'·'준호님'을 쓰지 않고, 자기는 undefined를 돌려줍니다.

흔한 오답은 [ '민지님', '준호님' ]입니다. forEach를 map처럼 본 것입니다. 그 배열이 필요하면 forEach 자리에 map을 씁니다.

컴퓨터에서 해 보기(선택) — 점수에 들지 않습니다. 폰으로만 보고 있다면 건너뛰세요. 2절 코드를 main.js에 저장해 돌린 뒤, 3번 줄의 함수를 (p) => { return p + 500; },⁠처럼 중괄호 몸으로 바꿔 봅니다. 다음에는 return을 지워 봅니다. 무엇이 찍힐지 먼저 적고, 바꿔 돌려서 맞춰 봅니다. 저장하고 돌리는 법은 내 컴퓨터에서 돌려 보기에 있습니다.

끝까지 내려오면 저절로 표시됩니다. 이 기기에만 저장합니다.

자바스크립트 목차로