이번 편의 결과물: list --filter done|pending, list --sort created|due가 동작합니다. · 다루는 개념: 함수 선언 방식과 화살표 함수, 클로저, filter/sort/map
이 편에서 만드는 파일
todo-cli/
└── src/
└── index.js (~)개념 정리
함수를 선언하는 세 가지 방식
| 방식 | 예시 | 특징 |
|---|---|---|
| 함수 선언 | function add(a, b) { return a + b; } | 호이스팅됨(선언 전에 호출 가능) |
| 함수 표현식 | const add = function (a, b) { return a + b; }; | 변수에 담긴 뒤에만 호출 가능 |
| 화살표 함수 | const add = (a, b) => a + b; | this를 새로 만들지 않음, 간결한 표기 |
이 프로젝트는 화살표 함수와 함수 선언을 함께 씁니다. 짧은 콜백은 화살표 함수, 이름이 필요한 로직은 함수 선언을 사용합니다.
클로저
클로저(closure)는 함수가 자신이 정의될 때의 스코프를 기억해, 함수 밖으로 나간 뒤에도 그 스코프의 변수에 접근할 수 있는 것을 말합니다. 아래 makeStatusFilter는 status 값을 기억하는 함수를 반환합니다.
function makeStatusFilter(status) {
return (todo) => todo.done === (status === 'done');
}
const isDone = makeStatusFilter('done');
isDone({ done: true }); // true, status는 makeStatusFilter 실행이 끝난 뒤에도 기억됨makeStatusFilter 실행은 끝났지만 반환된 화살표 함수는 status를 계속 참조합니다. 이것이 클로저입니다.
고차 함수: filter, sort
고차 함수(higher-order function)는 함수를 인자로 받거나 함수를 반환하는 함수입니다. filter와 sort는 조건 함수(비교 함수)를 인자로 받는 대표적인 배열 고차 함수입니다.
| 메서드 | 인자로 받는 함수 | 반환값 | 원본 배열 |
|---|---|---|---|
filter(fn) | (item) => boolean | 조건을 만족하는 항목만 담긴 새 배열 | 변경 안 함 |
sort(fn) | (a, b) => number | 정렬된 배열(원본과 같은 참조) | 변경함 |
sort는 원본 배열을 직접 바꾸는 유일한 메서드입니다. 원본을 보존하는 toSorted는 16편(최신 문법)에서 다룹니다. 비교 함수는 a가 b보다 앞에 와야 하면 음수, 뒤에 와야 하면 양수를 반환해야 합니다.
실습
1. src/index.js 열기
03편의 todos 배열과 addTodo, printList는 그대로 두고 필터·정렬 함수를 추가합니다.
2. 코드 작성
// src/index.js
import { parseArgs } from './cli/parseArgs.js';
const todos = [
{
id: 1,
title: '리포트 제출',
done: false,
tags: ['work'],
due: '2026-09-20',
createdAt: '2026-09-15T09:00:00.000Z',
},
{
id: 2,
title: '운동하기',
done: true,
tags: ['health'],
due: null,
createdAt: '2026-09-15T10:00:00.000Z',
},
{
id: 3,
title: 'todo-cli에 검색 명령 추가',
done: false,
tags: ['study', 'node'],
due: '2026-09-22',
createdAt: '2026-09-16T08:10:00.000Z',
},
];
function nextId(list) {
const last = list[list.length - 1];
return last ? last.id + 1 : 1;
}
function addTodo(list, title, { tag, due } = {}) {
const todo = {
id: nextId(list),
title,
done: false,
tags: tag ? [tag] : [],
due: due ?? null,
createdAt: new Date().toISOString(),
};
list.push(todo);
return todo;
}
function makeStatusFilter(status) {
if (status === 'done') return (todo) => todo.done;
if (status === 'pending') return (todo) => !todo.done;
return () => true;
}
function makeComparator(sortKey) {
if (sortKey === 'due') {
return (a, b) => {
if (a.due === null && b.due === null) return 0;
if (a.due === null) return 1;
if (b.due === null) return -1;
return a.due.localeCompare(b.due);
};
}
return (a, b) => a.createdAt.localeCompare(b.createdAt);
}
function printList(list) {
list.forEach(({ id, title, done, tags, due }) => {
const mark = done ? '[x]' : '[ ]';
const tagText = tags.length > 0 ? tags.join(', ') : '없음';
const dueText = due ?? '없음';
console.log(`${mark} #${id} ${title} (태그: ${tagText}, 마감: ${dueText})`);
});
}
const argv = process.argv.slice(2);
const { command, positional, flags } = parseArgs(argv);
if (command === 'add') {
const [title] = positional;
const created = addTodo(todos, title, flags);
console.log('추가됨:', created);
printList(todos);
} else if (command === 'list') {
const filterFn = makeStatusFilter(flags.filter);
const compareFn = makeComparator(flags.sort);
const result = todos.filter(filterFn).sort(compareFn);
printList(result);
} else {
console.log('알 수 없는 명령입니다:', command);
}makeStatusFilter와 makeComparator는 둘 다 함수를 반환하는 고차 함수이고, 반환된 함수는 클로저로 바깥 매개변수를 기억합니다. flags.filter나 flags.sort가 없으면 각각 “전부 통과”와 “생성일순” 기본 동작으로 떨어집니다.
3. 실행
node src/index.js list --filter pendingnode src/index.js list --sort due확인
--filter pending결과(완료되지 않은 항목만):
[ ] #1 리포트 제출 (태그: work, 마감: 2026-09-20)
[ ] #3 todo-cli에 검색 명령 추가 (태그: study, node, 마감: 2026-09-22)--sort due결과(마감일 빠른 순,null은 맨 뒤):
[ ] #1 리포트 제출 (태그: work, 마감: 2026-09-20)
[ ] #3 todo-cli에 검색 명령 추가 (태그: study, node, 마감: 2026-09-22)
[x] #2 운동하기 (태그: health, 마감: 없음)직접 해보기
flags.filter에'done'도'pending'도 아닌 값을 넣고 실행해makeStatusFilter가 기본값(전부 통과)으로 동작하는지 확인합니다.title기준 정렬을 지원하는--sort title을makeComparator에 추가해봅니다.
정답 보기
function makeComparator(sortKey) {
if (sortKey === 'due') {
return (a, b) => {
if (a.due === null && b.due === null) return 0;
if (a.due === null) return 1;
if (b.due === null) return -1;
return a.due.localeCompare(b.due);
};
}
if (sortKey === 'title') {
return (a, b) => a.title.localeCompare(b.title);
}
return (a, b) => a.createdAt.localeCompare(b.createdAt);
}자주 하는 실수
| 증상 | 원인 | 고치는 법 |
|---|---|---|
| 정렬 결과가 뒤죽박죽 나옴 | 비교 함수가 boolean을 반환함(a.due > b.due만 반환) | 반드시 음수/0/양수를 반환하는 비교 함수를 만든다 |
list 두 번 실행하면 순서가 계속 바뀜 | sort가 원본 배열을 직접 변경함 | 원본을 유지하려면 [...todos].sort(...)처럼 복사 후 정렬한다 |
filter 결과가 항상 빈 배열 | 조건 함수가 undefined를 반환(화살표 함수 중괄호 안에서 return 누락) | 화살표 함수 본문에 중괄호를 쓰면 return을 명시한다 |