/** * 공통 유틸리티 함수 모듈 (Utils.js) * ======================================== * 자주 쓰는 헬퍼 함수들을 모아둔 정적 클래스입니다. * * [초보자용 사용 예] * Utils.delay(1000) → 1초 대기 후 Promise 반환 (async/await와 함께) * Utils.debounce(fn, 300) → 입력 등 연속 호출 방지 (마지막 호출만 실행) * Utils.throttle(fn, 100) → resize 등 빈번한 이벤트 제한 (100ms마다 1번) * Utils.formatDate(date) → "YYYY-MM-DD" 형식 * Utils.formatNumber(1234) → "1,234" (천단위 콤마) * Utils.storage.set('key', value) → localStorage 쉽게 사용 * * @module Utils */ class Utils { /** * 딜레이 함수 * @param {number} ms - 지연 시간 (밀리초) * @returns {Promise} */ static delay(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } /** * 디바운스 함수 * @param {Function} func - 실행할 함수 * @param {number} wait - 대기 시간 * @returns {Function} */ static debounce(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; } /** * 쓰로틀 함수 * @param {Function} func - 실행할 함수 * @param {number} limit - 제한 시간 * @returns {Function} */ static throttle(func, limit) { let inThrottle; return function (...args) { if (!inThrottle) { func.apply(this, args); inThrottle = true; setTimeout(() => (inThrottle = false), limit); } }; } /** * 랜덤 ID 생성 * @param {number} length - ID 길이 * @returns {string} */ static generateId(length = 8) { return Math.random() .toString(36) .substring(2, length + 2); } /** * 깊은 복사 * @param {*} obj - 복사할 객체 * @returns {*} */ static deepClone(obj) { if (obj === null || typeof obj !== "object") return obj; if (obj instanceof Date) return new Date(obj.getTime()); if (obj instanceof Array) return obj.map((item) => this.deepClone(item)); const clonedObj = {}; for (const key in obj) { if (obj.hasOwnProperty(key)) { clonedObj[key] = this.deepClone(obj[key]); } } return clonedObj; } /** * 객체 병합 * @param {Object} target - 대상 객체 * @param {Object} source - 소스 객체 * @returns {Object} */ static mergeDeep(target, source) { const output = { ...target }; if (this.isObject(target) && this.isObject(source)) { Object.keys(source).forEach((key) => { if (this.isObject(source[key])) { if (!(key in target)) { Object.assign(output, { [key]: source[key] }); } else { output[key] = this.mergeDeep(target[key], source[key]); } } else { Object.assign(output, { [key]: source[key] }); } }); } return output; } /** * 객체 확인 * @param {*} item - 확인할 항목 * @returns {boolean} */ static isObject(item) { return item && typeof item === "object" && !Array.isArray(item); } /** * URL 파라미터 파싱 * @param {string} url - URL 문자열 * @returns {Object} */ static parseUrlParams(url = window.location.search) { const params = new URLSearchParams(url); const result = {}; for (const [key, value] of params) { result[key] = value; } return result; } /** * 퍼센트 계산 * @param {number} current - 현재 값 * @param {number} total - 전체 값 * @param {number} decimals - 소수점 자리수 * @returns {number} */ static calculatePercent(current, total, decimals = 0) { if (total === 0) return 0; const percent = (current / total) * 100; return decimals > 0 ? parseFloat(percent.toFixed(decimals)) : Math.round(percent); } /** * 배열 섞기 (Fisher-Yates shuffle) * @param {Array} array - 섞을 배열 * @returns {Array} */ static 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; } /** * 배열 청크 분할 * @param {Array} array - 분할할 배열 * @param {number} size - 청크 크기 * @returns {Array} */ static chunkArray(array, size) { const chunks = []; for (let i = 0; i < array.length; i += size) { chunks.push(array.slice(i, i + size)); } return chunks; } /** * 로컬 스토리지 관리 */ static storage = { set(key, value) { try { localStorage.setItem(key, JSON.stringify(value)); return true; } catch (e) { console.error("Storage set error:", e); return false; } }, get(key, defaultValue = null) { try { const item = localStorage.getItem(key); return item ? JSON.parse(item) : defaultValue; } catch (e) { console.error("Storage get error:", e); return defaultValue; } }, remove(key) { try { localStorage.removeItem(key); return true; } catch (e) { console.error("Storage remove error:", e); return false; } }, clear() { try { localStorage.clear(); return true; } catch (e) { console.error("Storage clear error:", e); return false; } }, }; /** * 날짜 포맷팅 * @param {Date} date - 포맷할 날짜 * @param {string} format - 포맷 문자열 (YYYY-MM-DD, YYYY.MM.DD 등) * @returns {string} */ static formatDate(date, format = "YYYY-MM-DD") { const d = new Date(date); const year = d.getFullYear(); const month = String(d.getMonth() + 1).padStart(2, "0"); const day = String(d.getDate()).padStart(2, "0"); const hour = String(d.getHours()).padStart(2, "0"); const minute = String(d.getMinutes()).padStart(2, "0"); const second = String(d.getSeconds()).padStart(2, "0"); return format .replace("YYYY", year) .replace("MM", month) .replace("DD", day) .replace("HH", hour) .replace("mm", minute) .replace("ss", second); } /** * 숫자 포맷팅 (천단위 콤마) * @param {number} num - 포맷할 숫자 * @returns {string} */ static formatNumber(num) { return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); } /** * 파일 크기 포맷팅 * @param {number} bytes - 바이트 크기 * @param {number} decimals - 소수점 자리수 * @returns {string} */ static formatFileSize(bytes, decimals = 2) { if (bytes === 0) return "0 Bytes"; const k = 1024; const dm = decimals < 0 ? 0 : decimals; const sizes = ["Bytes", "KB", "MB", "GB", "TB"]; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i]; } } // ES6 모듈 내보내기 if (typeof module !== "undefined" && module.exports) { module.exports = Utils; }