배열에서 필요한 값만 골라낼 때 자주 사용하는 메서드가 filter()입니다. 그런데 아래처럼 조건이 없어 보이는 코드를 만나면 헷갈릴 수 있습니다.
my_string.split(" ").filter(v => v);
“filter가 원래 빈 값을 제거해 주는 걸까?” “v => v는 어떤 조건일까?”
핵심은 filter()에 전달한 함수의 반환값입니다. 기본 사용법부터 헷갈리기 쉬운 사례까지 살펴보겠습니다.
1. filter()는 조건을 통과한 요소로 새 배열을 만든다
filter()는 전달한 함수를 배열 요소마다 실행하고, 반환값이 참으로 판단되는 요소만 새 배열에 담습니다. 조건에 맞는 요소가 없으면 빈 배열을 반환합니다.
const numbers = [1, 2, 3, 4, 5];
const result = numbers.filter(number => number > 3);
console.log(result); // [4, 5]
| 요소 | number > 3 | 결과 |
|---|---|---|
| 1 | false | 제외 |
| 2 | false | 제외 |
| 3 | false | 제외 |
| 4 | true | 포함 |
| 5 | true | 포함 |
조건이 true이면 그 요소를 남깁니다. “조건에 맞는 요소를 삭제한다”라고 생각하면 반대로 이해하게 됩니다.
[1, 2, 3].filter(v => v === 2); // [2]: 2만 남기기
[1, 2, 3].filter(v => v !== 2); // [1, 3]: 2 제외하기
2. filter()에 전달하는 함수는 콜백 함수다
콜백 함수는 다른 함수에 인수로 전달하고, 그 함수가 필요한 시점에 실행하는 함수입니다.
const numbers = [1, 2, 3, 4];
const result = numbers.filter(function (number) {
return number % 2 === 0;
});
console.log(result); // [2, 4]
여기서는 filter()가 콜백 함수에 요소를 하나씩 전달합니다. 위 코드는 화살표 함수로 짧게 쓸 수 있습니다.
numbers.filter(number => number % 2 === 0);
콜백 함수는 다음 세 가지 인수를 받을 수 있습니다.
array.filter((value, index, array) => {
return 조건;
});
| 매개변수 | 의미 |
|---|---|
| value | 현재 검사하는 요소 |
| index | 현재 요소의 인덱스 |
| array | filter()를 호출한 배열 |
매개변수 이름은 자유롭게 정할 수 있고, 필요한 인수만 받으면 됩니다. 세 번째 인수는 결과 배열이 아니라 검사 대상 배열입니다.
const letters = ["a", "b", "c", "d"];
letters.filter((value, index) => index % 2 === 0);
// ["a", "c"]: 인덱스가 0, 2인 요소
3. v => v는 값을 그대로 반환하는 함수다
다음 세 가지 표현은 같은 의미입니다.
v => v
(v) => {
return v;
}
function (v) {
return v;
}
왼쪽의 v는 전달받는 값이고, 오른쪽의 v는 반환하는 값입니다. v가 특별한 문법은 아니므로 word => word로 작성해도 같습니다.
중괄호 없이 표현식을 작성하면 그 결과를 자동으로 반환합니다.
그렇다면 문자열을 반환했는데도 filter가 동작하는 이유는 무엇일까요?
4. 반드시 true나 false를 반환할 필요는 없다
JavaScript는 조건을 판단할 때 다른 값도 참이나 거짓으로 해석합니다.
- truthy: 참으로 판단되는 값
- falsy: 거짓으로 판단되는 값
filter()도 이 규칙을 사용합니다.
| 값 | 판단 |
|---|---|
"" |
falsy |
0, -0, 0n |
falsy |
false |
falsy |
null, undefined |
falsy |
NaN |
falsy |
"hello", " " |
truthy |
"0", "false" |
truthy |
1, -1 |
truthy |
[], {} |
truthy |
위 표는 일반적인 코드에서 만나는 값들을 정리한 것입니다.
const values = ["", "hello", 0, 1, false, null, undefined];
values.filter(v => v);
// ["hello", 1]
각 값을 그대로 반환하면, 그 값의 truthy/falsy 여부로 포함 여부가 결정됩니다.
숫자 0과 문자열 "0"은 다르다
[0, "0", false, "false"].filter(v => v);
// ["0", "false"]
문자열은 내용이 비어 있는지가 중요합니다. "0"과 "false"는 비어 있지 않은 문자열이므로 남습니다.
빈 배열과 빈 객체도 남는다
[[], {}, ""].filter(v => v);
// [[], {}]
“비어 있다”는 일상적인 의미와 falsy는 다릅니다. 빈 배열과 빈 객체는 truthy입니다.
5. 빈 문자열 ""과 공백 문자열 " "은 다르다
Boolean(""); // false
Boolean(" "); // true
Boolean(" "); // true
""에는 문자가 없지만, " "에는 공백 문자가 들어 있습니다.
const words = ["", " ", "hello", " "];
words.filter(v => v);
// [" ", "hello", " "]
공백만 있는 문자열도 제외하려면 trim()으로 앞뒤 공백을 제거한 결과를 검사합니다.
words.filter(v => v.trim() !== "");
// ["hello"]
이 예제는 모든 요소가 문자열인 배열을 전제로 합니다. null 등에는 trim()을 직접 호출할 수 없습니다.
또한 검사할 때 trim()을 사용했다고 반환되는 문자열까지 바뀌지는 않습니다.
[" hello ", " "].filter(v => v.trim() !== "");
// [" hello "]
앞뒤 공백을 실제로 없애려면 값을 바꾸는 map()도 사용합니다.
[" hello ", " "]
.map(v => v.trim())
.filter(v => v !== "");
// ["hello"]
6. split(" ").filter(v => v)는 어떻게 동작할까?
공백으로 단어를 구분하는 상황을 살펴보겠습니다.
const my_string = " i love you ";
const words = my_string.split(" ");
console.log(words);
// ["", "i", "", "love", "you", ""]
const result = words.filter(v => v);
console.log(result);
// ["i", "love", "you"]
split(" ")은 공백 하나를 구분자로 사용합니다. 연속된 구분자 사이 또는 문자열의 처음·끝에 구분자가 있으면 빈 문자열이 생길 수 있습니다.
filter(v => v)는 이렇게 생긴 빈 문자열을 제외합니다.
이 배열에는 문자열만 들어 있으므로 다음 표현도 결과가 같습니다.
words.filter(v => v);
words.filter(v => v !== "");
words.filter(v => v.length > 0);
words.filter(Boolean);
하지만 어떤 배열에서든 위 표현들이 같은 것은 아닙니다.
const values = [0, false, "", "hello"];
values.filter(v => v);
// ["hello"]
values.filter(v => v !== "");
// [0, false, "hello"]
v => v는 모든 falsy 값을 제외하고, v => v !== ""는 빈 문자열만 제외합니다.
7. filter(Boolean)은 왜 동작할까?
Boolean()은 값을 불리언으로 변환합니다.
Boolean("hello"); // true
Boolean(""); // false
Boolean(0); // false
filter(Boolean)은 Boolean 함수를 콜백으로 전달하는 표현입니다.
const values = ["", "hello", 0, 1];
values.filter(Boolean);
// ["hello", 1]
// 이 경우 다음과 같은 결과
values.filter(v => Boolean(v));
values.filter(v => v);
코드는 짧지만, 숫자 0이나 false를 유지해야 하는 상황에서는 조건을 명확하게 작성해야 합니다.
const scores = [0, 80, null, 100, undefined];
scores.filter(Boolean);
// [80, 100]: 유효한 점수인 0까지 제외됨
scores.filter(v => v !== null && v !== undefined);
// [0, 80, 100]
8. 중괄호를 쓰고 return을 빼먹으면 모두 제외된다
자주 하는 실수는 다음 코드입니다.
const numbers = [1, 2, 3, 4];
numbers.filter(v => {
v > 2;
});
// []
v > 2를 계산하지만 결과를 반환하지 않습니다. 반환값은 undefined가 되고, falsy이므로 모든 요소가 제외됩니다.
다음 두 방식 중 하나로 작성해야 합니다.
// 표현식의 결과를 자동으로 반환
numbers.filter(v => v > 2);
// [3, 4]
// 중괄호를 사용하면 return을 직접 작성
numbers.filter(v => {
return v > 2;
});
// [3, 4]
9. filter()는 값을 바꾸는 메서드가 아니다
콜백에서 반환한 값은 포함 여부를 판단하는 데 쓰입니다. 결과 배열에는 원래 요소가 들어갑니다.
const numbers = [1, 2, 3];
numbers.filter(v => v * 2);
// [1, 2, 3]
v * 2의 결과는 각각 2, 4, 6입니다. 모두 truthy이므로 원래 요소가 전부 남습니다.
각 값을 두 배로 바꾸려면 map()을 사용합니다.
numbers.map(v => v * 2);
// [2, 4, 6]
| 메서드 | 용도 | 반환 결과 |
|---|---|---|
| filter() | 조건에 맞는 요소 모두 선택 | 배열 |
| map() | 각 요소를 다른 값으로 변환 | 배열 |
| find() | 조건에 맞는 첫 요소 찾기 | 요소 또는 undefined |
| some() | 조건에 맞는 요소가 하나라도 있는지 확인 | true 또는 false |
const numbers = [1, 2, 3, 4];
numbers.filter(v => v > 2); // [3, 4]
numbers.find(v => v > 2); // 3
numbers.some(v => v > 2); // true
10. 새 배열이 만들어져도 객체는 공유된다
filter() 자체는 원본 배열에서 요소를 삭제하지 않습니다. 결과를 새 배열로 반환합니다.
const original = [1, 2, 3];
const filtered = original.filter(v => v > 1);
console.log(original); // [1, 2, 3]
console.log(filtered); // [2, 3]
console.log(original === filtered); // false
다만 배열 안에 객체가 있다면, 객체 자체를 새로 복사하지는 않습니다.
const users = [
{ name: "Heera", active: true },
{ name: "Min", active: false }
];
const activeUsers = users.filter(user => user.active);
console.log(activeUsers[0] === users[0]); // true
activeUsers[0].name = "Choi";
console.log(users[0].name); // "Choi"
새 배열과 원본 배열이 같은 객체를 참조하기 때문입니다. 이런 복사를 얕은 복사라고 합니다. 또한 콜백에서 원본 요소를 직접 수정하면 원본 데이터도 변경되므로, 조건 검사와 데이터 변경은 구분해서 작성하는 편이 좋습니다.
11. 실무에서 활용하기
완료된 항목만 선택하기
const tasks = [
{ id: 1, title: "React 공부", done: true },
{ id: 2, title: "블로그 작성", done: false },
{ id: 3, title: "코딩 문제 풀기", done: true }
];
const completed = tasks.filter(task => task.done);
// id가 1, 3인 객체가 담긴 배열
특정 항목을 제외하기
const items = [
{ id: 1, name: "사과" },
{ id: 2, name: "바나나" }
];
const remaining = items.filter(item => item.id !== 1);
// [{ id: 2, name: "바나나" }]
검색어를 포함한 문자열 찾기
const titles = ["React 기초", "JavaScript 배열", "React 상태 관리"];
const keyword = "react";
const result = titles.filter(title =>
title.toLowerCase().includes(keyword.toLowerCase())
);
// ["React 기초", "React 상태 관리"]
12. 헷갈릴 때는 “무엇을 반환하는가?”를 확인하자
array.filter(v => true); // 검사한 요소 모두 포함
array.filter(v => false); // 모두 제외
array.filter(v => v); // truthy인 요소만 포함
array.filter(v => v !== ""); // 빈 문자열만 제외
filter()에는 빈 문자열이나 공백을 자동으로 제거하는 기본 조건이 없습니다. 콜백의 반환값이 포함 여부를 결정하고, 선택된 원래 요소가 새 배열에 담깁니다.
관련 문서:
'프론트엔드 > JavaScript' 카테고리의 다른 글
| [ JavaScript ] slice, splice, substring, substr 차이점 정리: 기초부터 예제까지 (0) | 2026.10.19 |
|---|---|
| [ JavaScript ] sort() 메서드와 sort((a, b) => a - b)의 차이점 (0) | 2025.01.29 |
| [ JavaScript ] findIndex와 indexOf의 차이점 알아보기 (0) | 2024.10.17 |
| [ javascript ] HTML, CSS, JS로 음악 플레이어 만들기 (7) | 2024.09.09 |
| [ Javascript ] Drop Down(드롭다운) 메뉴 만들기 (2) | 2024.04.23 |
댓글