브라우저에서 파일을 다루는 일은 생각보다 자주 발생합니다. 이미지 업로드 미리보기, CSV 다운로드, 파일 드래그 앤 드롭 등이 모두 File API와 Blob을 기반으로 동작합니다.

Blob이란?

Blob(Binary Large Object)은 바이너리 데이터의 불변 객체입니다.

JS
// 텍스트 Blob 생성
const textBlob = new Blob(["Hello, World!"], { type: "text/plain" });
console.log(textBlob.size); // 13
console.log(textBlob.type); // "text/plain"

// JSON Blob
const jsonBlob = new Blob([JSON.stringify({ name: "정훈" })], {
  type: "application/json",
});

// 여러 조각 합치기
const combined = new Blob(["Part 1, ", "Part 2, ", "Part 3"]);

File 객체

File은 Blob을 확장한 객체로, 파일 이름과 수정 날짜 정보가 추가됩니다.

JS
// input으로 파일 선택
const input = document.querySelector('input[type="file"]');
input.addEventListener("change", (e) => {
  const file = e.target.files[0];
  console.log(file.name);         // "photo.jpg"
  console.log(file.size);         // 바이트 단위
  console.log(file.type);         // "image/jpeg"
  console.log(file.lastModified); // 타임스탬프
});

// 프로그래밍적으로 File 생성
const file = new File(["내용"], "test.txt", {
  type: "text/plain",
  lastModified: Date.now(),
});

FileReader — 파일 읽기

JS
function readFile(file) {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onload = () => resolve(reader.result);
    reader.onerror = () => reject(reader.error);
    reader.readAsText(file); // 텍스트로 읽기
  });
}

// 다양한 읽기 방식
const reader = new FileReader();
reader.readAsText(file);        // 텍스트
reader.readAsDataURL(file);     // Base64 Data URL
reader.readAsArrayBuffer(file); // ArrayBuffer

모던 방식 — Blob 메서드

JS
// FileReader 없이 직접 읽기 (더 간결)
const text = await file.text();
const buffer = await file.arrayBuffer();
const stream = file.stream();

이미지 미리보기

JS
// 방법 1: FileReader (Data URL)
function previewWithReader(file, imgElement) {
  const reader = new FileReader();
  reader.onload = (e) => {
    imgElement.src = e.target.result;
  };
  reader.readAsDataURL(file);
}

// 방법 2: Object URL (더 효율적)
function previewWithObjectURL(file, imgElement) {
  const url = URL.createObjectURL(file);
  imgElement.src = url;
  imgElement.onload = () => URL.revokeObjectURL(url); // 메모리 해제
}

Object URL은 Data URL보다 메모리 효율적이고 빠릅니다.

파일 다운로드

JS
// 텍스트 파일 다운로드
function downloadText(content, filename) {
  const blob = new Blob([content], { type: "text/plain" });
  const url = URL.createObjectURL(blob);
  const a = document.createElement("a");
  a.href = url;
  a.download = filename;
  a.click();
  URL.revokeObjectURL(url);
}

downloadText("안녕하세요!", "greeting.txt");

// CSV 다운로드
function downloadCSV(data, filename) {
  const csv = data.map((row) => row.join(",")).join("\n");
  const bom = "\uFEFF"; // 한글 깨짐 방지 BOM
  const blob = new Blob([bom + csv], { type: "text/csv;charset=utf-8" });
  const url = URL.createObjectURL(blob);
  const a = document.createElement("a");
  a.href = url;
  a.download = filename;
  a.click();
  URL.revokeObjectURL(url);
}

downloadCSV(
  [["이름", "나이"], ["정훈", "25"], ["길동", "30"]],
  "users.csv"
);

파일 크기 검증

JS
function validateFile(file, options = {}) {
  const { maxSize = 5 * 1024 * 1024, allowedTypes = [] } = options;
  const errors = [];

  if (file.size > maxSize) {
    errors.push(`파일 크기가 ${formatBytes(maxSize)}를 초과합니다.`);
  }

  if (allowedTypes.length > 0 && !allowedTypes.includes(file.type)) {
    errors.push(`허용되지 않는 파일 형식입니다: ${file.type}`);
  }

  return errors;
}

function formatBytes(bytes) {
  if (bytes === 0) return "0 B";
  const units = ["B", "KB", "MB", "GB"];
  const i = Math.floor(Math.log(bytes) / Math.log(1024));
  return `${(bytes / Math.pow(1024, i)).toFixed(1)} ${units[i]}`;
}

// 사용
const errors = validateFile(file, {
  maxSize: 10 * 1024 * 1024, // 10MB
  allowedTypes: ["image/jpeg", "image/png", "image/webp"],
});

Blob 슬라이싱 — 대용량 파일 분할 업로드

JS
async function uploadInChunks(file, url, chunkSize = 1024 * 1024) {
  const totalChunks = Math.ceil(file.size / chunkSize);

  for (let i = 0; i < totalChunks; i++) {
    const start = i * chunkSize;
    const end = Math.min(start + chunkSize, file.size);
    const chunk = file.slice(start, end);

    const formData = new FormData();
    formData.append("chunk", chunk);
    formData.append("chunkIndex", i);
    formData.append("totalChunks", totalChunks);
    formData.append("fileName", file.name);

    await fetch(url, { method: "POST", body: formData });
    console.log(`업로드 진행: ${Math.round(((i + 1) / totalChunks) * 100)}%`);
  }
}

fetch 응답을 파일로 저장

JS
async function downloadFromServer(url, filename) {
  const response = await fetch(url);
  const blob = await response.blob();

  const objectUrl = URL.createObjectURL(blob);
  const a = document.createElement("a");
  a.href = objectUrl;
  a.download = filename;
  a.click();
  URL.revokeObjectURL(objectUrl);
}

**기억하기 **: Blob은 바이너리 데이터 컨테이너이고, File은 Blob에 이름을 추가한 것입니다. 미리보기에는 URL.createObjectURL이 효율적이고, 다운로드에는 Blob + <a> 태그 패턴을 사용합니다. Object URL은 사용 후 반드시 revokeObjectURL로 해제해야 메모리 누수를 방지합니다.

댓글 로딩 중...