JavaScript 함수와 객체를 import·export로 두 모듈에 어떻게 나누나요?
함수는 입력으로 결과를 만들고 객체는 관련 값을 묶습니다. export로 다른 파일에서 쓸 항목을 내보내고 import로 가져옵니다. 두 .mjs 파일에서 데이터 처리와 결과 출력을 나누어 봅니다.
적용 조건·범위: 출력이 목적이라면 문제가 없지만 계산 결과 자체가 필요한 경우에는 반환값을 두는 편이 낫습니다.
공식 문서 참고: MDN: ES 모듈의 import·export와 브라우저 실행
독립 실습: 두 파일로 함수와 데이터를 분리하기
JavaScript 함수와 객체, import export로 모듈 나누기 · 독립 실습
두 모듈에서 같은 store를 읽는 출력과 import/export 소스를 비교하세요.
예제 크게 보기 (새 탭)TaskFlow ZIP 없이 같은 폴더에 두 파일을 만듭니다. .mjs는 Node.js가 ES Module로 읽는 확장자이므로 이 예제에 별도의 package.json은 필요 없습니다.
task-store.mjs
export function createTask(id, title) {
return { id, title, completed: false };
}
export function countPending(tasks) {
let count = 0;
for (const task of tasks) {
if (!task.completed) count += 1;
}
return count;
}
app.mjs
import { createTask, countPending } from './task-store.mjs';
const tasks = [
createTask(1, '문서 정리'),
{ id: 2, title: '메일 확인', completed: true },
];
console.log('미완료: ' + countPending(tasks));
console.log('새 작업 완료 여부: ' + tasks[0].completed);
터미널에서 두 파일이 있는 폴더로 이동한 뒤 실행합니다.
node app.mjs
예상 출력:
미완료: 1
새 작업 완료 여부: false
createTask는 새 작업 객체를 반환하고 countPending은 배열을 받아 미완료 개수를 반환합니다. 출력은 진입점인 app.mjs가 맡습니다. 이것이 반환값과 출력, 데이터 함수와 실행 파일의 경계입니다.
스스로 확인: app.mjs의 두 console.log 앞에 tasks[0].completed = true;를 한 줄 추가하면 무엇이 출력될까요? ‘미완료: 0’, ‘새 작업 완료 여부: true’입니다. import 오류가 나면 두 파일이 같은 폴더인지, 이름·확장자와 ./가 일치하는지 확인하세요.
함수는 입력을 받아 결과를 돌려주는 단위입니다
function calculatePrice(price, quantity) {
return price * quantity;
}
const total = calculatePrice(4500, 2);
console.log(total);
return으로 값을 돌려주면 호출한 쪽에서 출력, 저장, 다른 계산 등 다음 동작을 선택할 수 있습니다.
반대로 함수 내부에서 모든 일을 처리하면 재사용하기 어려워집니다.
function printPrice(price, quantity) {
console.log(price * quantity);
}
출력이 목적이라면 문제가 없지만 계산 결과 자체가 필요한 경우에는 반환값을 두는 편이 낫습니다.

객체로 관련 값을 묶기
const product = {
name: '드립 커피',
price: 4500,
};
console.log(product.name);
TaskFlow의 작업도 같은 방식입니다.
const task = {
id: 1,
title: 'JavaScript 공부',
completed: false,
};
서로 관련된 값을 하나의 객체로 묶으면 함수가 여러 매개변수를 따로 받을 필요가 줄어듭니다.
객체 생성 규칙을 함수로 모으기
function createTask(id, title) {
return {
id,
title,
completed: false,
};
}
이제 작업 생성 규칙은 한곳에 있습니다.
ES Module로 파일 책임 나누기
task-store.js가 데이터를 관리하고 app.js가 앱의 진입점을 맡도록 나눠보겠습니다.
named export:
export function createTask() {}
export function addTask() {}
가져오기:
import { createTask, addTask } from './task-store.js';
named export는 내보낸 이름을 그대로 맞춰야 합니다.
default export는 언제 사용할까
한 파일의 대표 값이 명확할 때 사용할 수 있습니다.
export default function createProduct() {}
하지만 TaskFlow store처럼 여러 공개 함수가 동등한 역할을 한다면 named export만 사용하는 편이 경계를 읽기 쉽습니다. 이 과정의 TaskFlow 코어는 named export 중심으로 진행합니다.
브라우저에서 모듈 실행
브라우저에서 실행할 때는 index.html에 다음 모듈 script를 연결합니다. 독립 Node.js 실습에는 이 HTML 연결이 필요하지 않습니다.
<script type="module" src="./js/app.js"></script>
ES Module은 로컬 서버 주소에서 실행합니다. 파일을 더블클릭해 file://로 열었을 때 모듈 요청이 차단되면 코드 자체의 import 문법 문제로 오해하지 마세요.
Node에서 같은 모듈 검증하기
브라우저용 .js 파일을 Node에서도 import하려면 프로젝트 루트에 간단한 package.json을 둘 수 있습니다.
{
"type": "module"
}
이제 .js를 ES Module로 해석합니다.
자주 발생하는 오류
export 이름이 맞지 않습니다
import { addTodo } from './task-store.js';
실제 export가 addTask라면 가져올 수 없습니다.
상대 경로의 ./를 빠뜨립니다
브라우저 로컬 모듈은 패키지 이름이 아니라 파일 상대 경로를 정확히 적어야 합니다.
함수가 undefined를 반환합니다
계산은 했지만 return을 빠뜨렸는지 확인합니다.
학습 완료 체크리스트
- 매개변수와 반환값을 설명할 수 있습니다.
- 객체로 관련 값을 묶을 수 있습니다.
- named export/import를 사용할 수 있습니다.
- 함수와 모듈의 책임을 구분할 수 있습니다.
- import 경로 오류를 점검할 수 있습니다.
독립 실습은 여기서 마칠 수 있습니다. 아래 선택 실습은 TaskFlow 프로젝트 파일로 진행합니다.
선택 실습: 할 일 앱에 적용하기
TaskFlow는 작업을 추가·완료·삭제하는 할 일 앱입니다. 이 선택 실습에서는 JavaScript로 화면에 동작을 붙입니다.
task-store.js에 작업 상태와 함수를 모으고 app.js에서 import해 호출합니다.
선택: 연재 프로젝트와 전체 자료
연재 프로젝트를 이어 만드는 분을 위한 자료입니다. 이 글의 개념과 예제만 찾으셨다면 필요한 파일과 실행 방법만 펼쳐보세요.
TaskFlow 프로젝트 적용
JavaScript 함수와 객체, import export로 모듈 나누기 · 단계별 TaskFlow
TaskFlow JavaScript 3단계입니다. 기존 서비스 화면과 실제 콘솔 출력을 확인하세요.
예제 크게 보기 (새 탭)변경 설명과 부분 예제를 읽은 뒤, 시작·완료 파일과 적용 순서에서 필요한 선언·import·화면 연결을 포함한 전체 파일을 확인하세요. 이번 단계의 변경 이유와 적용 위치도 함께 확인합니다.
시작본과 이번 변경
JavaScript 조건문과 반복문: 변수 값의 흐름부터 추적하기의 완료 체크포인트를 시작본으로 사용합니다.
js/task-store.js에 데이터 함수를 분리하고 js/app.js에서 named import로 호출합니다. Node에서 .js 모듈을 실행할 때는 프로젝트 package.json의 type을 module로 맞춥니다. 초기 3개와 추가 후 미완료 목록을 확인하세요.
연속 프로젝트 선수 조건
2회차의 tasks 데이터 구조를 그대로 사용합니다.
{ id, title, completed }
이전 체크포인트
javascript-step-02-control-flow
파일 구조
index.html
style.css
package.json
js/
app.js
task-store.js
전체 파일·코드 보기
package.json
{
"type": "module"
}
js/task-store.js 전체 코드
let tasks = [
{ id: '1', title: 'JavaScript 과정 정리', completed: false },
{ id: '2', title: 'TaskFlow 화면 확인', completed: true },
{ id: '3', title: '다음 글 읽기', completed: false },
];
export function getTasks() {
return tasks;
}
export function addTask(title) {
const task = {
id: String(Date.now()),
title,
completed: false,
};
tasks.push(task);
return task;
}
export function toggleTask(id) {
const task = tasks.find((item) => item.id === id);
if (!task) {
return null;
}
task.completed = !task.completed;
return task;
}
export function deleteTask(id) {
tasks = tasks.filter((task) => task.id !== id);
}
export function filterTasks(status = 'all') {
if (status === 'completed') {
return tasks.filter((task) => task.completed);
}
if (status === 'pending') {
return tasks.filter((task) => !task.completed);
}
return tasks;
}
현재 toggleTask()는 객체를 직접 바꿉니다. 일부러 남겨둔 부분이며 5회차에서 객체 참조 문제를 확인한 뒤 불변 갱신으로 바꿉니다.
js/app.js 전체 코드
import {
addTask,
filterTasks,
getTasks,
} from './task-store.js';
console.log(getTasks());
addTask('모듈 예제 복습');
console.log(filterTasks('pending'));
이번 회차 변경 코드와 전체본 연결
기존 HTML/CSS는 유지하고 이번 체크포인트에서 추가·변경되는 JavaScript 전체 파일은 다음과 같습니다.
검증
브라우저 Console에서 초기 작업 3개와 새 작업이 추가된 미완료 목록을 확인합니다.
터미널에서는 다음처럼 모듈 로딩 자체를 확인할 수 있습니다.
node js/app.js
중간부터 시작하는 전체 파일과 적용 순서
시작 ZIP은 직전 프로젝트 단계 javascript-step-02-control-flow의 실제 완료 파일입니다. 프로젝트 순서를 이어가는 기준이며 사이트의 기술 읽기 순서와는 구분합니다.
- 시작 ZIP을 새 폴더에 풉니다. 이 폴더가 작업 루트이며 기존 체크포인트 파일을 별도로 찾을 필요가 없습니다.
- task-store.js를 추가하고 app.js에서 공개 함수를 import합니다. 빠른 연속 추가도 서로 다른 ID를 갖도록 보강 전체본은 randomUUID를 사용합니다.
- 아래 변경 파일을 같은 상대 경로에 추가하거나 전체 교체합니다. 표시하지 않은 파일과 images는 시작본 그대로 유지합니다. package-lock.json은 완료 ZIP에 있는 파일을 함께 적용합니다.
- 작업 루트에서 명령을 실행하고 예상 결과를 확인합니다. 완료 ZIP은 직접 적용한 결과를 비교하는 기준입니다.
추가·교체 파일
CHANGES.md— 전체 교체README.md— 전체 교체js/app.js— 전체 교체js/task-store.js— 추가
실행과 확인
npm start
npm test
개발 서버는 실행 상태로 두고 다른 터미널에서 검사 명령을 실행하세요. Node.js 24.18.0에서 검증했습니다. JavaScript는 http://127.0.0.1:4173, TypeScript는 Vite 실행 출력의 주소로 접속합니다.
예상 결과: 초기 목록과 모듈 예제 복습을 추가한 미완료 목록이 출력됩니다.
실제 검증: JavaScript 모듈·DOM·경계값 실행 검사 통과. 시작 ZIP에 이 글의 변경 파일만 적용한 결과와 완료 ZIP의 모든 파일 내용이 일치했습니다. 브라우저 화면 배치·실제 기기·외부 CDN 가용성은 이 검사에 포함하지 않았습니다.
이 절의 전체 파일은 ZIP과 일치합니다. 위쪽 원문의 짧은 예제나 생략 표시가 있는 부분 코드는 개념·변경 위치 설명용입니다. 서버 저장과 인증은 제공하지 않으며 JSON fixture는 로컬 읽기 실습용입니다.
복사할 변경 파일 전체 코드
js/app.js — 전체 파일
import {
addTask,
filterTasks,
getTasks,
} from './task-store.js';
console.log(getTasks());
addTask('모듈 예제 복습');
console.log(filterTasks('pending'));
js/task-store.js — 전체 파일
let tasks = [
{ id: '1', title: 'JavaScript 과정 정리', completed: false },
{ id: '2', title: 'TaskFlow 화면 확인', completed: true },
{ id: '3', title: '다음 글 읽기', completed: false },
];
export function getTasks() {
return tasks;
}
export function addTask(title) {
const task = {
id: crypto.randomUUID(),
title,
completed: false,
};
tasks.push(task);
return task;
}
export function toggleTask(id) {
const task = tasks.find((item) => item.id === id);
if (!task) {
return null;
}
task.completed = !task.completed;
return task;
}
export function deleteTask(id) {
tasks = tasks.filter((task) => task.id !== id);
}
export function filterTasks(status = 'all') {
if (status === 'completed') {
return tasks.filter((task) => task.completed);
}
if (status === 'pending') {
return tasks.filter((task) => !task.completed);
}
return tasks;
}
완료 체크포인트
javascript-step-03-functions-modules
다음 학습
다음 글에서는 같은 함수 이름을 유지하면서 내부 구현에 map, filter, forEach, reduce를 적용합니다.
다음 글: JavaScript 배열 메서드: map filter forEach reduce 차이
글 소개·학습 준비·전체 목차

JavaScript · 3 / 15 · 필수 학습 · 함수·객체·ES Module
함수는 입력을 받아 결과를 반환하고, 객체는 관련 값을 묶으며, export와 import는 그 함수와 데이터를 다른 파일에서 사용할 경계를 만듭니다. 두 .mjs 파일에서 데이터 처리와 출력의 책임을 나누어 결과를 확인합니다.
이번 글에서는 함수에 책임을 나누고, 객체로 관련 값을 묶고, ES Module로 파일 경계를 만듭니다.
이번 글에서 배우는 내용
- 함수의 입력과 반환값
- 객체 프로퍼티
- 부수효과와 반환값 구분
- named export와 import
- default export를 사용할 때의 기준
- 모듈 경로 오류
- TaskFlow 상태 로직 분리
먼저 알아야 할 내용
변수·배열·반복문을 알면 됩니다. 아래 두 .mjs 파일은 작업 데이터까지 포함하므로 2회차 결과물이 필요하지 않습니다.
목차
이 글이 도움이 되었나요?
읽음 기록
읽음 — / 전체 15개 · 필수 14개
새 글 받아보기
RSS 리더에서 BlogFlow의 새 글을 확인할 수 있습니다.