배열과 객체의 검색
120분 안팎
학습 목표
배열과 객체로 지역·이름 조건을 함께 적용합니다.
개념
왜 화면 문자열 대신 객체를 보관합니까
행사 소개 문구에 서울이라는 글자가 있다는 이유로 지역 필터를 통과시키면 데이터의 실제 지역과 어긋납니다. 행사 한 건은 id, name, region, date 속성을 가진 객체로 표현하고 여러 건은 배열에 담습니다. 이름 검색은 name에, 지역 비교는 region에만 적용합니다. 속성의 역할을 나누어 두면 화면 문구를 바꾸어도 검색 기준은 유지됩니다.
배열의 인덱스는 0부터 시작합니다. events[0]은 첫 행사이며 events[0].name은 그 행사의 이름입니다. 배열 길이는 length로 읽습니다. id는 화면 순번과 다르며 필터나 정렬 후에도 같은 행사를 식별합니다. 지역은 앞 모듈 select의 값과 같은 seoul, busan, all 코드를 씁니다. 서울이라는 표시 문구를 코드와 혼용하지 않습니다.
filter는 배열의 각 요소를 콜백에 전달하고 반환값이 참으로 평가되는 요소만 새 배열에 담습니다. event => 조건은 매개변수 한 개를 받는 화살표 함수이며 식 하나를 바로 반환합니다. 중괄호 블록을 쓰면 return을 직접 적어야 합니다. filter의 콜백에서 console.log만 호출하면 undefined를 반환해 모든 요소가 빠질 수 있습니다.
계약을 코드로 옮깁니다
검색어를 레슨 2의 normalizeQuery로 한 번 계산합니다. 각 행사 name도 같은 규칙으로 바꾸고 includes(q)로 부분 문자열 포함 여부를 확인합니다. 빈 문자열은 모든 문자열에 포함되므로 공백 검색어는 이름 제한을 해제합니다. 지역 조건은 region === "all" 또는 정확한 코드 일치입니다. 이름 조건과 지역 조건은 &&로 묶어 두 조건을 함께 만족시키게 합니다.
map은 선택된 행사에서 원하는 필드를 꺼내 결과를 변환합니다. filterEvents의 반환값은 행사 객체 배열이고 채점용 출력은 그 배열에 map(event => event.id)를 적용한 id 배열입니다. filter와 map은 각각 선택과 변환을 맡습니다. 출력만 필요하다고 함수 안에서 id만 반환하면 이후 카드 렌더링에 name이나 date가 없어 다시 데이터를 찾게 됩니다.
이번 계약은 events가 배열이며 각 name과 region이 문자열인 행사 목록을 받습니다. 유효하지 않은 검색어 값은 빈 문자열로 정규화하지만 행사 객체의 구조는 제공 fixture가 보장합니다. 지역 all은 전체, 모르는 지역 코드는 일치하는 행사가 없어 빈 배열입니다. 원본 순서를 유지하고 데이터를 수정하지 않습니다. 데이터 검증과 네트워크 실패 처리는 뒤 API 모듈에서 확장합니다.
function normalizeQuery(value) {
if (typeof value !== 'string') return '';
return value.trim().replace(/\s+/g, ' ').toLowerCase();
}
function filterEvents(events, query, region) {
const q = normalizeQuery(query);
return events.filter(event =>
normalizeQuery(event.name).includes(q)
&& (region === 'all' || event.region === region));
}작은 목록으로 조건을 추적합니다
행사 세 건에서 책 검색과 부산 지역을 함께 적용합니다. 책이 들어 있어도 서울 행사면 탈락하고 부산 행사여도 이름에 책이 없으면 탈락합니다. 결과가 없다는 것은 함수 오류와 같지 않습니다. 존재하지 않는 지역의 빈 배열은 정상 결과입니다. 조건을 하나씩 풀어 비교한 뒤 함께 적용하면 어떤 조건 때문에 제외됐는지 설명할 수 있습니다.
filter가 새 배열을 준다고 객체까지 새로 만들어지는 것은 아닙니다. 반환 배열의 객체를 수정하지 않습니다. 이름 검색 과정에서 event.name에 소문자 값을 다시 저장하면 표시용 원본 문구를 잃습니다. normalizeQuery(event.name)의 계산값만 비교에 사용합니다. 원본 name과 배열 순서가 호출 전후 같은지 테스트에 포함합니다.
흔한 실수와 오류 읽기
TypeError에서 filter is not a function이면 events가 실제 배열인지 JSON 입력의 키가 맞는지 확인합니다. Cannot read properties of undefined는 없는 인덱스나 속성 경로를 읽었는지 살핍니다. 결과가 전부 빈 배열이면 콜백 return 누락, 지역 코드와 표시 문구 혼용, && 대신 다른 조건을 썼는지 확인합니다. 입력 JSON을 최소 세 건으로 줄여 각 조건 결과를 관찰합니다.
선택과 변환의 순서를 구분합니다
먼저 filter로 행사 객체를 선택하고 그 뒤 map으로 id를 꺼냅니다. id부터 꺼내면 이름과 지역 정보가 없어져 조건을 검사할 수 없습니다. filterEvents 안에서는 객체 배열을 유지하므로 화면을 만드는 호출자가 이름과 날짜를 함께 사용할 수 있습니다. 채점용 id 배열은 화면 데이터와 별도의 표현이며 같은 검색 결과를 어떤 모양으로 전달할지 정하는 마지막 단계입니다.
콜백의 입력을 추적합니다
filter 콜백의 event는 매번 현재 행사 한 건을 가리킵니다. events 전체나 첫 객체만 검사하는 것이 아닙니다. 세 행사의 이름 조건과 지역 조건을 두 열로 적고 두 열이 모두 참인 행만 남겨 봅니다. 콜백에서 객체를 반환해도 참으로 평가되지만 선택 규칙을 드러내기 위해 조건 결과를 반환합니다. 조건식에 중괄호를 추가할 때는 return을 빠뜨리지 않습니다.
검색이 원본 순서를 유지합니다
filter는 조건을 통과한 요소를 원본에서 만난 순서대로 새 배열에 담습니다. 부산 행사의 id가 10 다음 2로 들어왔다면 결과도 10 다음 2입니다. 숫자 크기나 날짜로 자동 정렬되지 않습니다. 이번 레슨의 기대 id 배열에는 입력 순서가 반영됩니다. 정렬 요구는 다음 레슨의 함수에서 처리하므로 필터 함수에 sort를 끼워 넣어 책임을 섞지 않습니다.
빈 결과와 전체 결과를 구별합니다
빈 검색어는 이름 제한을 풀지만 지역 제한까지 자동으로 풀지는 않습니다. 공백 검색어와 busan을 전달하면 부산 행사만 남습니다. 반대로 책과 all을 전달하면 모든 지역 중 이름에 책이 포함된 행사만 남습니다. 빈 목록은 어떤 조건에도 빈 결과를 냅니다. 모르는 지역은 제공 데이터에 일치하는 코드가 없다는 전제에서 빈 배열이며 오류 문자열을 대신 반환하지 않습니다.
객체 참조를 안전하게 사용합니다
결과 배열과 원본 배열은 서로 다른 배열이지만 포함된 행사 객체는 공유됩니다. 선택된 첫 행사를 다른 이름으로 수정하면 원본에서도 그 이름을 읽게 됩니다. 검색은 읽기 작업이므로 콜백에서 name이나 region에 값을 저장하지 않습니다. 정규화된 이름은 비교식 안에서만 사용합니다. 호출 전후 이름과 배열 순서를 비교하고 결과 객체가 원본 요소와 같은 참조라는 점도 설명합니다.
따라하기
정상 입력 추적
계산 결과를 먼저 예상합니다. 아래 코드를 Node의 실행 창이나 별도 example.cjs 파일에 입력합니다. 파일 실행 명령은 node example.cjs입니다. 각 단계는 독립된 예제이므로 이전 변수 선언과 섞지 않습니다.
function normalizeQuery(value) {
if (typeof value !== 'string') return '';
return value.trim().replace(/\s+/g, ' ').toLowerCase();
}
function filterEvents(events, query, region) {
const q = normalizeQuery(query);
return events.filter(event =>
normalizeQuery(event.name).includes(q)
&& (region === 'all' || event.region === region));
}
const events = [{"id": 3, "name": "동네 책 나눔", "region": "seoul", "date": "2026-10-20"}, {"id": 1, "name": "Book Fair", "region": "busan", "date": "2026-10-19"}, {"id": 2, "name": "마을 책 모임", "region": "busan", "date": "2026-10-20"}];
console.log(JSON.stringify(filterEvents(events," 책 ","busan").map(e=>e.id)));실행 결과
[2]
경계와 빈 값 확인
조건 하나를 바꿔 결과를 비교합니다. 아래 코드를 Node의 실행 창이나 별도 example.cjs 파일에 입력합니다. 파일 실행 명령은 node example.cjs입니다. 각 단계는 독립된 예제이므로 이전 변수 선언과 섞지 않습니다.
function normalizeQuery(value) {
if (typeof value !== 'string') return '';
return value.trim().replace(/\s+/g, ' ').toLowerCase();
}
function filterEvents(events, query, region) {
const q = normalizeQuery(query);
return events.filter(event =>
normalizeQuery(event.name).includes(q)
&& (region === 'all' || event.region === region));
}
const events = [{"id": 3, "name": "동네 책 나눔", "region": "seoul", "date": "2026-10-20"}, {"id": 1, "name": "Book Fair", "region": "busan", "date": "2026-10-19"}, {"id": 2, "name": "마을 책 모임", "region": "busan", "date": "2026-10-20"}];
console.log(JSON.stringify(filterEvents(events," ","all").map(e=>e.id)));실행 결과
[3,1,2]
호출 계약 점검
원본과 반환값의 책임을 확인합니다. 아래 코드를 Node의 실행 창이나 별도 example.cjs 파일에 입력합니다. 파일 실행 명령은 node example.cjs입니다. 각 단계는 독립된 예제이므로 이전 변수 선언과 섞지 않습니다.
function normalizeQuery(value) {
if (typeof value !== 'string') return '';
return value.trim().replace(/\s+/g, ' ').toLowerCase();
}
function filterEvents(events, query, region) {
const q = normalizeQuery(query);
return events.filter(event =>
normalizeQuery(event.name).includes(q)
&& (region === 'all' || event.region === region));
}
console.log(JSON.stringify(filterEvents([],"책","unknown")));실행 결과
[]
브라우저 실습과 미션으로 옮기기
입력은 {events,query,region}입니다. events의 각 객체는 숫자 id와 문자열 name·region·date를 가집니다. 검색어와 이름에 같은 정규화를 적용해 부분 문자열을 검사하고 지역 조건을 동시에 적용합니다. all은 전체이며 모르는 지역은 빈 결과입니다. 입력 순서의 id 배열을 JSON으로 출력하고 원본을 수정하지 않습니다.
아래 실습 starter의 TODO를 구현하고 모든 제공 입력을 실행합니다. 출력은 JSON 한 개만 남깁니다. Node 로컬에서는 main.cjs에 코드를 저장하고 다음 명령으로 입력 파일을 전달할 수 있습니다. input.json에는 아래 tests의 입력 한 개를 저장합니다.
node main.cjs < input.json미션에서는 계산 함수를 assets/data.js에 옮깁니다. 표준 입력용 require와 console.log는 브라우저에 복사하지 않습니다. npm ci 후 npm test로 데이터 계약과 앞 모듈 문서·CSS 검사를 확인합니다.
확인 문제
실습
입력은 {events,query,region}입니다. events의 각 객체는 숫자 id와 문자열 name·region·date를 가집니다. 검색어와 이름에 같은 정규화를 적용해 부분 문자열을 검사하고 지역 조건을 동시에 적용합니다. all은 전체이며 모르는 지역은 빈 결과입니다. 입력 순서의 id 배열을 JSON으로 출력하고 원본을 수정하지 않습니다.
모범 답안
const input = JSON.parse(require('fs').readFileSync(0,'utf8'));
function normalizeQuery(value) {
if (typeof value !== 'string') return '';
return value.trim().replace(/\s+/g, ' ').toLowerCase();
}
function filterEvents(events, query, region) {
const q = normalizeQuery(query);
return events.filter(event =>
normalizeQuery(event.name).includes(q)
&& (region === 'all' || event.region === region));
}
console.log(JSON.stringify(filterEvents(input.events,input.query,input.region).map(e=>e.id)));
더 읽기
면접 질문
- filter와 map의 역할과 반환값은 어떻게 다른가요?
- filter로 만든 새 배열의 객체를 수정하면 원본에 어떤 영향이 있나요?