JavaScript 치트시트
14. 유틸리티
14.1 JSON 처리
기본 사용법
const obj = { name: 'Alice', age: 25 };
const jsonStr = JSON.stringify(obj);
console.log(jsonStr); // '{"name":"Alice","age":25}'
const parsedObj = JSON.parse(jsonStr);
console.log(parsedObj.name); // Alice
실무 활용: API 통신 및 로컬 스토리지
// API 요청 시 데이터 전송
const userData = {
id: 123,
name: '김철수',
email: 'kim@example.com',
preferences: { theme: 'dark', language: 'ko' },
};
// 서버로 전송
fetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(userData),
});
// 로컬 스토리지 저장/로드
localStorage.setItem('userPrefs', JSON.stringify(userData.preferences));
const savedPrefs = JSON.parse(localStorage.getItem('userPrefs') || '{}');
// 안전한 JSON 파싱 (에러 처리)
function safeJsonParse(jsonString, defaultValue = null) {
try {
return JSON.parse(jsonString);
} catch (error) {
console.error('JSON 파싱 실패:', error);
return defaultValue;
}
}
// 깊은 복사에 활용 (단순 객체만)
const deepCopy = JSON.parse(JSON.stringify(originalObj));
JSON.stringify 고급 옵션
const data = {
name: '홍길동',
password: 'secret123',
age: 30,
createdAt: new Date(),
};
// 특정 필드 제외
const filtered = JSON.stringify(data, ['name', 'age']);
// 함수를 이용한 필터링
const secured = JSON.stringify(data, (key, value) => {
if (key === 'password') return undefined;
return value;
});
// 들여쓰기로 가독성 향상
const pretty = JSON.stringify(data, null, 2);
14.2 날짜와 시간 처리
UTC 시간을 한국 시간(UTC+9)으로 변환
// UTC 시간을 한국 시간으로 변환
function utcToKST(utcDateString) {
const utcDate = new Date(utcDateString);
const kstOffset = 9 * 60; // 9시간을 분으로 변환
const kstTime = new Date(utcDate.getTime() + kstOffset * 60 * 1000);
return kstTime;
}
// 사용 예
const utcTime = '2024-01-15T10:30:00Z';
const kstTime = utcToKST(utcTime);
console.log('UTC:', utcTime);
console.log('KST:', kstTime.toISOString());
// 더 간단한 방법: toLocaleString 사용
const utcDate = new Date('2024-01-15T10:30:00Z');
const kstString = utcDate.toLocaleString('ko-KR', {
timeZone: 'Asia/Seoul',
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
});
실무에서 자주 사용하는 날짜 유틸리티
// 현재 시간을 다양한 형식으로
const now = new Date();
// ISO 형식 (서버 통신용)
console.log(now.toISOString()); // 2024-01-15T10:30:00.000Z
// 한국 형식
console.log(now.toLocaleString('ko-KR')); // 2024. 1. 15. 오후 7:30:00
// 사용자 정의 형식
const customFormat = now.toLocaleDateString('ko-KR', {
year: 'numeric',
month: 'long',
day: 'numeric',
weekday: 'long',
});
console.log(customFormat); // 2024년 1월 15일 월요일
// 날짜 계산 유틸리티
function addDays(date, days) {
const result = new Date(date);
result.setDate(result.getDate() + days);
return result;
}
function getDateDifference(date1, date2) {
const diffTime = Math.abs(date2 - date1);
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
return diffDays;
}
// 날짜 형식 검증
function isValidDate(dateString) {
const date = new Date(dateString);
return date instanceof Date && !isNaN(date);
}
// 비즈니스 날짜 계산
function isWeekend(date) {
const day = date.getDay();
return day === 0 || day === 6; // 일요일(0) 또는 토요일(6)
}
function getBusinessDays(startDate, endDate) {
let count = 0;
const current = new Date(startDate);
while (current <= endDate) {
if (!isWeekend(current)) {
count++;
}
current.setDate(current.getDate() + 1);
}
return count;
}
타임존 처리 및 변환
// 다양한 타임존 변환
function convertTimezone(date, fromTz, toTz) {
return new Date(date.toLocaleString('en-US', { timeZone: fromTz })).toLocaleString('en-US', {
timeZone: toTz,
});
}
// 서버 시간(UTC)과 클라이언트 시간 동기화
class TimeManager {
constructor() {
this.serverOffset = 0; // 서버와의 시간 차이
}
async syncWithServer() {
const start = Date.now();
const response = await fetch('/api/time');
const serverTime = await response.json();
const end = Date.now();
// 네트워크 지연 보정
const networkDelay = (end - start) / 2;
this.serverOffset = serverTime.timestamp - (start + networkDelay);
}
getServerTime() {
return new Date(Date.now() + this.serverOffset);
}
}
14.3 정규 표현식
기본 검증 패턴
const patterns = {
email: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
phone: /^01[0-9]-\d{3,4}-\d{4}$/,
korean: /^[가-힣]+$/,
password: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/,
url: /^https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)$/,
};
// 유효성 검사 함수
function validateInput(value, type) {
return patterns[type]?.test(value) || false;
}
// 사용 예
console.log(validateInput('test@example.com', 'email')); // true
console.log(validateInput('010-1234-5678', 'phone')); // true
문자열 처리 및 추출
// HTML 태그 제거
function stripHtmlTags(html) {
return html.replace(/<[^>]*>/g, '');
}
// 숫자만 추출
function extractNumbers(text) {
return text.match(/\d+/g) || [];
}
// 문자열에서 URL 추출
function extractUrls(text) {
const urlRegex =
/https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/g;
return text.match(urlRegex) || [];
}
// 텍스트 마스킹 (개인정보 보호)
function maskEmail(email) {
return email.replace(/(.{2})(.*)(@.*)/, '$1***$3');
}
function maskPhoneNumber(phone) {
return phone.replace(/(\d{3})-(\d{4})-(\d{4})/, '$1-****-$3');
}
// 파일 확장자 검증
function isValidImageFile(filename) {
return /\.(jpg|jpeg|png|gif|webp)$/i.test(filename);
}
14.4 URL 처리 (URL, URLSearchParams) - 실무 활용
고급 URL 처리
// URL 파라미터 관리 클래스
class UrlManager {
constructor(url = window.location.href) {
this.url = new URL(url);
}
// 파라미터 추가/수정
setParam(key, value) {
this.url.searchParams.set(key, value);
return this;
}
// 파라미터 삭제
removeParam(key) {
this.url.searchParams.delete(key);
return this;
}
// 파라미터 가져오기
getParam(key, defaultValue = null) {
return this.url.searchParams.get(key) || defaultValue;
}
// 모든 파라미터를 객체로
getAllParams() {
const params = {};
for (const [key, value] of this.url.searchParams) {
params[key] = value;
}
return params;
}
// URL 문자열 반환
toString() {
return this.url.toString();
}
// 히스토리 업데이트 (페이지 새로고침 없이)
updateHistory() {
window.history.pushState({}, '', this.toString());
}
}
// 사용 예
const urlManager = new UrlManager();
urlManager
.setParam('page', '2')
.setParam('sort', 'date')
.setParam('filter', 'active')
.updateHistory();
API URL 빌더
class ApiUrlBuilder {
constructor(baseUrl) {
this.baseUrl = baseUrl;
this.segments = [];
this.params = new URLSearchParams();
}
path(...segments) {
this.segments.push(...segments);
return this;
}
query(key, value) {
if (value !== null && value !== undefined) {
this.params.set(key, value);
}
return this;
}
queries(obj) {
Object.entries(obj).forEach(([key, value]) => {
if (value !== null && value !== undefined) {
this.params.set(key, value);
}
});
return this;
}
build() {
let url = this.baseUrl;
if (this.segments.length > 0) {
url += '/' + this.segments.join('/');
}
if (this.params.toString()) {
url += '?' + this.params.toString();
}
return url;
}
}
// 사용 예
const apiUrl = new ApiUrlBuilder('https://api.example.com')
.path('users', '123', 'posts')
.queries({
page: 1,
limit: 20,
sort: 'created_at',
include: 'comments',
})
.build();
// 결과: https://api.example.com/users/123/posts?page=1&limit=20&sort=created_at&include=comments
14.5 난수 생성 및 고유 ID 생성
다양한 난수 생성
// 범위 내 정수 생성
function randomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
// 범위 내 실수 생성
function randomFloat(min, max) {
return Math.random() * (max - min) + min;
}
// 배열에서 랜덤 요소 선택
function randomChoice(array) {
return array[Math.floor(Math.random() * array.length)];
}
// 배열 셔플
function shuffleArray(array) {
const shuffled = [...array];
for (let i = shuffled.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
}
return shuffled;
}
// 가중치가 있는 랜덤 선택
function weightedRandom(items, weights) {
const totalWeight = weights.reduce((sum, weight) => sum + weight, 0);
let random = Math.random() * totalWeight;
for (let i = 0; i < items.length; i++) {
random -= weights[i];
if (random <= 0) {
return items[i];
}
}
return items[items.length - 1];
}
고유 ID 생성
// 간단한 UUID 생성 (실무용)
function generateUUID() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
const r = (Math.random() * 16) | 0;
const v = c === 'x' ? r : (r & 0x3) | 0x8;
return v.toString(16);
});
}
// 짧은 고유 ID 생성
function generateShortId(length = 8) {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let result = '';
for (let i = 0; i < length; i++) {
result += chars.charAt(Math.floor(Math.random() * chars.length));
}
return result;
}
// 타임스탬프 기반 ID
function generateTimestampId() {
return Date.now().toString(36) + Math.random().toString(36).substr(2);
}
// 시퀀셜 ID 생성기
class IdGenerator {
constructor(prefix = '') {
this.prefix = prefix;
this.counter = 0;
}
next() {
return `${this.prefix}${++this.counter}`;
}
reset() {
this.counter = 0;
}
}
// Nano ID 스타일 (더 안전한 URL-safe ID)
function generateNanoId(size = 21) {
const alphabet = '_-0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
let id = '';
let bytes = crypto.getRandomValues(new Uint8Array(size));
for (let i = 0; i < size; i++) {
id += alphabet[bytes[i] & 63];
}
return id;
}
14.6 실무 필수 유틸리티 함수들
데이터 변환 및 검증
// 안전한 타입 변환
function safeParseInt(value, defaultValue = 0) {
const parsed = parseInt(value, 10);
return isNaN(parsed) ? defaultValue : parsed;
}
function safeParseFloat(value, defaultValue = 0.0) {
const parsed = parseFloat(value);
return isNaN(parsed) ? defaultValue : parsed;
}
// 빈 값 체크
function isEmpty(value) {
return (
value === null ||
value === undefined ||
value === '' ||
(Array.isArray(value) && value.length === 0) ||
(typeof value === 'object' && Object.keys(value).length === 0)
);
}
// 객체 깊은 병합
function deepMerge(target, source) {
const result = { ...target };
for (const key in source) {
if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
result[key] = deepMerge(result[key] || {}, source[key]);
} else {
result[key] = source[key];
}
}
return result;
}
// 디바운스 (검색, API 호출 최적화)
function debounce(func, delay) {
let timeoutId;
return function (...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => func.apply(this, args), delay);
};
}
// 쓰로틀 (스크롤, 리사이즈 이벤트 최적화)
function throttle(func, delay) {
let lastTime = 0;
return function (...args) {
const now = Date.now();
if (now - lastTime >= delay) {
lastTime = now;
func.apply(this, args);
}
};
}
에러 처리 및 로깅
// 안전한 함수 실행
function safeExecute(fn, defaultValue = null) {
try {
return fn();
} catch (error) {
console.error('Function execution failed:', error);
return defaultValue;
}
}
// 비동기 함수 재시도
async function retry(fn, maxAttempts = 3, delay = 1000) {
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
return await fn();
} catch (error) {
if (attempt === maxAttempts) {
throw error;
}
console.warn(`Attempt ${attempt} failed, retrying in ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
// 성능 측정
function measurePerformance(name, fn) {
const start = performance.now();
const result = fn();
const end = performance.now();
console.log(`${name} took ${end - start} milliseconds`);
return result;
}
브라우저 환경 감지
// 디바이스 및 브라우저 감지
const DeviceDetector = {
isMobile: () =>
/Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent),
isTablet: () => /iPad|Android(?!.*Mobile)/i.test(navigator.userAgent),
isDesktop: () => !DeviceDetector.isMobile() && !DeviceDetector.isTablet(),
isIOS: () => /iPad|iPhone|iPod/.test(navigator.userAgent),
isAndroid: () => /Android/.test(navigator.userAgent),
isSafari: () => /^((?!chrome|android).)*safari/i.test(navigator.userAgent),
isChrome: () => /Chrome/.test(navigator.userAgent) && /Google Inc/.test(navigator.vendor),
isFirefox: () => /Firefox/.test(navigator.userAgent),
};
// 기능 지원 확인
const FeatureDetector = {
supportsLocalStorage: () => {
try {
const test = 'test';
localStorage.setItem(test, test);
localStorage.removeItem(test);
return true;
} catch (e) {
return false;
}
},
supportsWebP: () => {
const canvas = document.createElement('canvas');
return canvas.toDataURL('image/webp').indexOf('data:image/webp') === 0;
},
supportsTouchEvents: () => 'ontouchstart' in window,
supportsIntersectionObserver: () => 'IntersectionObserver' in window,
};