localStorage/sessionStorage의 차이, setItem/getItem/removeItem 기본 조작, JSON 직렬화로 객체 저장, 실전 패턴(테마·장바구니·폼 자동저장), 저장 한도·private 브라우징 주의사항까지 완전 정복합니다.
🎯 학습 목표
이 강을 마치면 다음을 할 수 있습니다.
localStorage와sessionStorage의 차이를 이해하고 상황에 맞게 선택할 수 있습니다.- 문자열과 객체를 브라우저에 저장하고 불러올 수 있습니다.
- 테마 설정, 장바구니, 폼 자동 저장 같은 실전 패턴을 구현할 수 있습니다.
- 저장 용량 한계와 private 브라우징 주의사항을 알고 방어 코드를 작성할 수 있습니다.
💡 Web Storage란 무엇인가
이 섹션에서는 브라우저 저장소의 종류와 Web Storage가 필요한 이유를 살펴보겠습니다.
웹 앱에서 데이터를 저장하는 방법은 크게 서버 저장(DB)과 클라이언트 저장(브라우저)으로 나뉩니다. 사용자의 개인 설정, 장바구니, 임시 입력값처럼 서버에 보내기 전 잠시 또는 영구적으로 브라우저에 유지해야 하는 데이터에 클라이언트 저장소가 필요합니다.
브라우저 저장소에는 여러 종류가 있습니다. 과거에는 쿠키(Cookie)를 사용했지만 최대 4KB 용량 제한과 모든 HTTP 요청에 자동 첨부되는 불편함이 있었습니다. HTML5 이후 Web Storage API가 등장해 쿠키의 한계를 극복했습니다. Web Storage는 localStorage와 sessionStorage 두 가지로 구성됩니다.
// 브라우저 저장소 종류 비교
// Cookie : 최대 4KB, 서버에 자동 전송, 만료 설정 가능
// localStorage : 최대 5~10MB, 탭·브라우저 닫아도 유지, 동일 출처 공유
// sessionStorage: 최대 5~10MB, 탭 닫으면 삭제, 탭마다 독립
console.log(typeof window.localStorage); // 'object'
console.log(typeof window.sessionStorage); // 'object'
// 브라우저에서 직접 확인
// Chrome DevTools → Application 탭 → Storage 섹션
localStorage는 브라우저를 완전히 닫았다가 다시 열어도 데이터가 유지됩니다. 같은 출처(프로토콜+도메인+포트)의 모든 탭과 창에서 공유됩니다. 따라서 사용자 테마 설정, 언어 선택, 로그인 토큰처럼 장기 보존이 필요한 데이터에 적합합니다. sessionStorage는 탭을 닫으면 사라집니다. 같은 도메인이라도 탭마다 별개의 저장 공간을 사용합니다. 주문 진행 중 임시 데이터, 한 세션 동안만 유효한 인증 상태처럼 브라우저를 닫으면 초기화되어야 하는 데이터에 적합합니다.
🔧 기본 API — CRUD 조작
이 섹션에서는 Web Storage의 네 가지 기본 메서드를 살펴보겠습니다.
localStorage와 sessionStorage는 동일한 API를 제공합니다. 중요한 제약이 하나 있습니다. 키(key)와 값(value) 모두 반드시 문자열이어야 합니다. 숫자, 불리언, 배열, 객체를 저장하려면 JSON 변환이 필요합니다.
// 저장
localStorage.setItem('username', '홍길동');
localStorage.setItem('theme', 'dark');
localStorage.setItem('count', '42'); // 숫자도 문자열로!
// 조회
const name = localStorage.getItem('username'); // '홍길동'
const theme = localStorage.getItem('theme'); // 'dark'
const count = localStorage.getItem('missing'); // null (없으면 null)
// 삭제
localStorage.removeItem('count'); // 특정 항목 삭제
// 전체 삭제 (주의!)
// localStorage.clear();
// 저장된 항목 개수
console.log(localStorage.length); // 2
// 키 이름 목록 순회
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i); // 키 이름
const val = localStorage.getItem(key); // 값
console.log(key, ':', val);
}
// sessionStorage는 완전히 동일한 API
sessionStorage.setItem('cart_step', '2');
const step = sessionStorage.getItem('cart_step');
getItem()은 해당 키가 없으면 null을 반환합니다. undefined가 아닌 null이므로 조건 분기 시 if (value !== null)로 확인해야 합니다. localStorage.clear()는 해당 출처의 모든 항목을 지워버리므로, 다른 기능의 데이터도 함께 삭제될 수 있어 특별한 경우가 아니면 사용하지 않는 것이 좋습니다.
📦 객체·배열 저장 — JSON 직렬화
이 섹션에서는 문자열만 허용하는 Web Storage에 객체와 배열을 저장하는 JSON 변환 패턴을 살펴보겠습니다.
문자열만 저장 가능한 localStorage에 복잡한 데이터를 넣으려면 직렬화(Serialization)가 필요합니다. JSON.stringify()로 객체를 JSON 문자열로 변환해 저장하고, JSON.parse()로 읽어서 다시 객체로 복원합니다.
// 객체 저장
const user = { id: 1, name: '홍길동', role: 'student' };
localStorage.setItem('user', JSON.stringify(user));
// 객체 불러오기
const stored = localStorage.getItem('user');
const parsed = JSON.parse(stored); // 다시 객체로
console.log(parsed.name); // '홍길동'
// 배열 저장
const recentItems = ['사과', '바나나', '딸기'];
localStorage.setItem('recentItems', JSON.stringify(recentItems));
const items = JSON.parse(localStorage.getItem('recentItems') ?? '[]');
console.log(items.length); // 3
// 오류에 강한 헬퍼 함수 — 실전에서 권장
function getItem(key, defaultValue = null) {
try {
const item = localStorage.getItem(key);
return item !== null ? JSON.parse(item) : defaultValue;
} catch {
// JSON 파싱 실패, 손상된 데이터 등
return defaultValue;
}
}
function setItem(key, value) {
try {
localStorage.setItem(key, JSON.stringify(value));
return true;
} catch (err) {
// QuotaExceededError: 용량 초과
console.error('localStorage 저장 실패:', err);
return false;
}
}
// 사용
const userProfile = getItem('userProfile', { theme: 'light', lang: 'ko' });
setItem('userProfile', { theme: 'dark', lang: 'ko' });
저장하면 안 되는 데이터도 있습니다. 함수(JSON.stringify(() => {})는 undefined), undefined 자체, 순환 참조 객체는 JSON.stringify()에서 오류가 나거나 null이 됩니다. 또한 날짜 객체는 문자열로 변환되므로, 다시 읽을 때 new Date(parsed.date)로 복원해야 합니다. 민감한 정보(비밀번호, 카드번호)는 절대 localStorage에 저장하지 마세요 — 자바스크립트로 접근 가능해 XSS 공격에 취약합니다.
🎨 실전 패턴 ① — 다크모드 테마 설정
이 섹션에서는 가장 흔한 localStorage 활용 예시인 테마 설정 저장을 구현해 보겠습니다.
// 테마 설정 저장 및 적용
const THEME_KEY = 'app_theme';
function getTheme() {
return localStorage.getItem(THEME_KEY) ?? 'light'; // 기본값 'light'
}
function setTheme(theme) {
localStorage.setItem(THEME_KEY, theme);
document.documentElement.setAttribute('data-theme', theme);
// 또는 classList 토글
document.body.classList.toggle('dark', theme === 'dark');
}
// 페이지 로드 시 저장된 테마 적용
setTheme(getTheme());
// 토글 버튼
const toggleBtn = document.querySelector('#theme-toggle');
toggleBtn.addEventListener('click', () => {
const current = getTheme();
setTheme(current === 'light' ? 'dark' : 'light');
});
// 시스템 테마 감지 + localStorage 우선 적용
function getInitialTheme() {
const stored = localStorage.getItem(THEME_KEY);
if (stored) return stored; // 저장된 값 우선
// 저장값 없으면 시스템 설정 따르기
return window.matchMedia('(prefers-color-scheme: dark)').matches
? 'dark'
: 'light';
}
setTheme(getInitialTheme());
🛒 실전 패턴 ② — 장바구니
이 섹션에서는 서버 없이 브라우저에 장바구니 데이터를 유지하는 패턴을 구현해 보겠습니다.
const CART_KEY = 'shopping_cart';
// 장바구니 불러오기 (없으면 빈 배열)
function getCart() {
try {
return JSON.parse(localStorage.getItem(CART_KEY) ?? '[]');
} catch {
return [];
}
}
// 장바구니 저장
function saveCart(cart) {
localStorage.setItem(CART_KEY, JSON.stringify(cart));
}
// 상품 추가
function addToCart(product) {
const cart = getCart();
const existing = cart.find(item => item.id === product.id);
if (existing) {
existing.quantity += 1; // 이미 있으면 수량 증가
} else {
cart.push({ ...product, quantity: 1 });
}
saveCart(cart);
renderCartBadge(cart.length);
}
// 상품 제거
function removeFromCart(productId) {
const cart = getCart().filter(item => item.id !== productId);
saveCart(cart);
}
// 수량 변경
function updateQuantity(productId, quantity) {
const cart = getCart().map(item =>
item.id === productId ? { ...item, quantity } : item
);
saveCart(cart.filter(item => item.quantity > 0));
}
// 합계 계산
function getCartTotal() {
return getCart().reduce((total, item) => total + item.price * item.quantity, 0);
}
// 장바구니 뱃지 업데이트
function renderCartBadge(count) {
const badge = document.querySelector('#cart-badge');
if (badge) badge.textContent = count;
}
// 페이지 로드 시 장바구니 복원
renderCartBadge(getCart().length);
📝 실전 패턴 ③ — 폼 자동 저장
이 섹션에서는 긴 폼을 작성하다 실수로 새로 고침해도 내용이 살아있도록 자동 저장하는 패턴을 살펴보겠습니다.
const FORM_KEY = 'draft_post';
const titleInput = document.querySelector('#title');
const contentInput = document.querySelector('#content');
// 자동 저장 (500ms 디바운스)
let saveTimer;
function autoSave() {
clearTimeout(saveTimer);
saveTimer = setTimeout(() => {
const draft = {
title: titleInput.value,
content: contentInput.value,
savedAt: new Date().toISOString(),
};
localStorage.setItem(FORM_KEY, JSON.stringify(draft));
console.log('자동 저장됨:', draft.savedAt);
}, 500);
}
titleInput.addEventListener('input', autoSave);
contentInput.addEventListener('input', autoSave);
// 페이지 로드 시 임시 저장본 복원
function restoreDraft() {
try {
const saved = JSON.parse(localStorage.getItem(FORM_KEY));
if (!saved) return;
const savedAt = new Date(saved.savedAt);
const diffMin = (Date.now() - savedAt) / 60000;
if (diffMin < 60) { // 1시간 이내 임시 저장본만 복원
titleInput.value = saved.title ?? '';
contentInput.value = saved.content ?? '';
console.log('임시 저장본 복원됨 (' + Math.floor(diffMin) + '분 전)');
}
} catch {
localStorage.removeItem(FORM_KEY); // 손상된 데이터 삭제
}
}
restoreDraft();
// 제출 시 임시 저장본 삭제
document.querySelector('#post-form').addEventListener('submit', () => {
localStorage.removeItem(FORM_KEY);
});
⚠️ 주의사항과 한계
이 섹션에서는 localStorage를 실무에서 안전하게 사용하기 위해 반드시 알아야 할 주의사항을 살펴보겠습니다.
용량 한계: 브라우저마다 다르지만 보통 5~10MB 정도입니다. 대용량 데이터를 저장하려 하면 QuotaExceededError가 발생합니다. 따라서 항상 try/catch로 감싸는 것이 좋습니다.
동기(Synchronous) 방식: localStorage 조작은 동기 방식입니다. 메인 스레드를 차단하므로 매우 큰 데이터를 자주 읽고 쓰면 UI가 버벅일 수 있습니다. 대용량이 필요하면 IndexedDB(비동기, 용량 제한 없음)를 고려하세요.
동일 출처 정책: localStorage는 프로토콜+도메인+포트가 동일한 출처에서만 접근 가능합니다. http://와 https://는 다른 출처입니다.
Private 브라우징 모드: 시크릿 창에서는 localStorage가 탭을 닫으면 삭제됩니다. sessionStorage처럼 동작합니다. 접근 자체는 가능하지만 데이터가 유지되지 않을 수 있습니다.
보안: localStorage에 저장된 데이터는 같은 출처의 모든 자바스크립트에서 접근 가능합니다. XSS(Cross-Site Scripting) 공격이 성공하면 localStorage 전체를 읽을 수 있습니다. 따라서 JWT 액세스 토큰처럼 보안이 중요한 데이터는 httpOnly 쿠키에 저장하는 것이 더 안전합니다.
// 안전한 localStorage 사용 패턴
function safeGetItem(key, defaultValue = null) {
try {
const item = localStorage.getItem(key);
return item !== null ? JSON.parse(item) : defaultValue;
} catch {
return defaultValue;
}
}
function safeSetItem(key, value) {
try {
localStorage.setItem(key, JSON.stringify(value));
return true;
} catch (err) {
if (err.name === 'QuotaExceededError') {
console.warn('저장 공간 부족 — 오래된 항목 정리 후 재시도');
// 오래된 항목 정리 로직 추가 가능
}
return false;
}
}
// localStorage 가용 여부 확인 (private 모드 대응)
function isStorageAvailable() {
try {
localStorage.setItem('__test__', '1');
localStorage.removeItem('__test__');
return true;
} catch {
return false;
}
}
// 사용 전 체크
if (isStorageAvailable()) {
safeSetItem('theme', 'dark');
} else {
console.warn('localStorage 사용 불가 — 메모리에만 유지');
}
📝 핵심 요약
27강에서 배운 내용을 정리해 보겠습니다.
| 구분 | localStorage | sessionStorage |
|---|---|---|
| 유지 기간 | 영구 (수동 삭제까지) | 탭 닫으면 삭제 |
| 탭 공유 | 같은 출처 모두 공유 | 탭마다 독립 |
| 용도 | 테마, 언어, 북마크 | 임시 결제 정보, 세션 상태 |
| 메서드 | 설명 |
|---|---|
setItem(key, value) | 문자열 저장 (객체는 JSON.stringify 필요) |
getItem(key) | 조회 (없으면 null) |
removeItem(key) | 특정 키 삭제 |
clear() | 전체 삭제 (주의) |
다음 강인 28강 — 에러 처리와 디버깅에서는 코드의 버그를 효율적으로 찾고 수정하는 방법을 배웁니다. try/catch의 심화 활용, console의 다양한 메서드, Chrome DevTools 중단점 디버깅, 스택 트레이스 읽기까지 — 개발자라면 반드시 갖춰야 할 디버깅 역량을 익힙니다!
관련 주제
- localStorage
- sessionStorage
- JSON 직렬화
- 저장 용량 한계
- 테마 설정 저장
- 장바구니 저장 패턴
- 개발·프로그래밍
- 개발·프로그래밍 강의
- 자바스크립트 기초 30강 — 웹을 움직이는 언어
- 무료강의
- 무료 온라인 강의
- NUGUNA
- 누구나
📚 시리즈 전체 공유
자바스크립트 기초 30강 — 웹을 움직이는 언어
이 강의가 속한 시리즈는 총 30강, 모두 무료입니다. 처음부터 배우려는 동료에게 시리즈 전체를 알려 주세요.
댓글
불러오는 중...
