Initial commit: 교육 프로젝트 배포
This commit is contained in:
@@ -0,0 +1,277 @@
|
||||
/**
|
||||
* 공통 유틸리티 함수 모듈 (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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user