// 숫자 정렬 — 비교 함수 필수! (기본 sort는 문자열 비교)
const nums = [10, 2, 33, 4];
nums.sort((a, b) => a - b); // [2, 4, 10, 33]
// 필드 기준 (나이 → 이름)
users.sort((a, b) => a.age - b.age || a.name.localeCompare(b.name));
// 그룹핑 → { city: [user, ...] }
const byCity = users.reduce((acc, u) => {
(acc[u.city] ??= []).push(u);
return acc;
}, {});
💡 sort는 원본 배열을 바꾼다(in-place). 기본 정렬은 원소를 문자열로 비교하므로 숫자엔 반드시 (a,b)=>a-b.
문자열 다루기 (자르기 · 나누기 · 치환)
문자열
const s = " Hello, World ";
s.trim(); // 앞뒤 공백 제거
s.toLowerCase();
s.includes("World"); // true
s.startsWith(" He"); // true
s.replaceAll("l", "L"); // 모두 치환 (replace는 첫 개만)
// 자르기 · 나누기 · 잇기
const parts = "a,b,c".split(","); // ["a","b","c"]
const joined = parts.join("-"); // "a-b-c"
const sub = "abcdef".slice(1, 4); // "bcd"
템플릿 리터럴 · 자리 채우기
문자열
const name = "홍길동", age = 30;
// 템플릿 리터럴(백틱) — 변수 삽입
const msg = `${name}님은 ${age}세`;
const multi = `첫 줄
둘째 줄`; // 줄바꿈 그대로 유지
// 자리 채우기 · 반복
"7".padStart(3, "0"); // "007"
"9".padEnd(3, "-"); // "9--"
"=".repeat(20);
💡 작은따옴표가 아니라 백틱(`)이다. ${...} 안에는 식을 넣을 수 있다.
숫자·통화 표시 (천단위 콤마 · 원화 · 퍼센트)
문자열
const n = 1234567.891;
// 천단위 콤마
n.toLocaleString(); // "1,234,567.891"
n.toLocaleString("ko-KR", { maximumFractionDigits: 0 }); // "1,234,568"
// 통화 (원화는 소수 없음)
const krw = new Intl.NumberFormat("ko-KR", { style: "currency", currency: "KRW" });
krw.format(n); // "₩1,234,568"
// 퍼센트
new Intl.NumberFormat("ko-KR", { style: "percent", minimumFractionDigits: 1 })
.format(0.1234); // "12.3%"
// 소수 자리 고정 — toFixed는 "문자열"을 돌려준다
(3.14159).toFixed(2); // "3.14"
// 역방향: 콤마 문자열 → 숫자
Number("1,234,568".replaceAll(",", "")); // 1234568
💡 계산은 숫자로 끝내고 표시 직전에만 포맷 — 콤마 붙은 문자열로 계산하면 NaN. toFixed는 이진 오차 탓에 (2.675).toFixed(2)가 "2.67"이 되는 등 반올림이 어긋날 수 있다.
날짜 다루기 (포맷·더하기·차이)
날짜·시간
const now = new Date();
// 포맷
now.toISOString(); // "2026-07-07T12:30:00.000Z"
new Intl.DateTimeFormat("ko-KR").format(now); // "2026. 7. 7."
// 더하기 (밀리초 기반)
const tomorrow = new Date(now.getTime() + 24 * 60 * 60 * 1000);
// 두 날짜 차이(일)
const d1 = new Date("2026-07-07");
const d2 = new Date("2026-07-14");
const days = (d2 - d1) / (1000 * 60 * 60 * 24); // 7
💡 표준 Date는 포맷·연산이 빈약하다. 복잡해지면 date-fns(MIT) 같은 라이브러리를 고려.
날짜 비교 — 기준 형태로 완성해서 비교 (yyyyMMdd · yyyyMMddHHmmss · Date · 일 단위)
날짜·시간
// 쓰는 법: 비교할 기준 형태를 하나 고르고, 그 영역의 변환으로 값을 완성한 뒤 비교한다.
const pad = (n) => String(n).padStart(2, "0");
const now = new Date(); // 예시 값: 2026-08-02 09:30:00 을 담은 Date 객체
// ══ 1. "yyyyMMdd" 문자열로 완성해서 비교 — 완성형: "20260730" ══════
const s1 = "20260730143025".slice(0, 8); // "20260730143025" → "20260730"
const s2 = "" + now.getFullYear() + pad(now.getMonth() + 1) + pad(now.getDate()); // Date → "20260802"
s1 === s2; // "20260730" === "20260802" → false
s1 > s2; // 이후인가 (같은 자릿수 문자열은 사전순 = 시간순)
// ══ 2. "yyyyMMddHHmmss" 문자열로 완성해서 비교 — 완성형: "20260730143025" ══
const t1 = "20260730" + "000000"; // "20260730" → "20260730000000" (자정 붙이기)
const t2 = s2 + pad(now.getHours()) + pad(now.getMinutes()) + pad(now.getSeconds()); // Date → "20260802093000"
t1 === t2; // 초까지 같은가
t1 < t2; // 이전인가
// ══ 3. Date(시각 포함)로 완성해서 비교 — 완성형: Date(2026-07-30 14:30:25) ══
// 압축 문자열은 new Date()가 못 읽는다 — 잘라서 조립 (월은 0부터라 -1)
const toDate = (s) => new Date(
+s.slice(0, 4), +s.slice(4, 6) - 1, +s.slice(6, 8),
+s.slice(8, 10) || 0, +s.slice(10, 12) || 0, +s.slice(12, 14) || 0,
); // "20260730143025" → Date(2026-07-30 14:30:25)
toDate("20260730143025").getTime() === now.getTime(); // ==는 객체 비교라 항상 false — 꼭 getTime()
toDate("20260730143025").getTime() > now.getTime(); // getTime() = epoch 밀리초 숫자로 비교
// ══ 4. 일 단위 Date로 완성해서 비교 — 완성형: Date(2026-07-30 00:00:00) ══
const day1 = toDate("20260730"); // "20260730" → Date(2026-07-30 00:00:00)
const day2 = new Date(now); day2.setHours(0, 0, 0, 0); // Date(…09:30:00) → Date(…00:00:00) 시각 지우기
day1.getTime() === day2.getTime(); // 같은 날인가
💡 서버(Java·DB)로 보내 비교할 값은 문자열(1·2번) 그대로 보낸다 — Date를 JSON에 실으면 UTC ISO("…T15:00:00.000Z")로 바뀌어 날짜가 하루 밀릴 수 있다. new Date("2026-07-30")처럼 구분자 있는 문자열도 UTC 자정으로 읽히는 함정 — 압축 문자열은 3번처럼 수동 파싱. 비교는 같은 자릿수끼리만.
💡 ORM은 바인딩·이스케이프를 알아서 처리한다. 스키마(prisma/schema.prisma) 정의 후 generate가 선행되어야 한다.
요소 조작 (val · text · html · show/hide · class)
DOM·이벤트 · jQuery
// 값 읽기/쓰기 — input은 val(), 일반 요소는 text()/html()
const name = $("#userName").val(); // 읽기
$("#userName").val("홍길동"); // 쓰기
$("#msg").text("저장되었습니다"); // 텍스트 (이스케이프됨 — 안전)
$("#box").html("<b>굵게</b>"); // HTML로 해석 — 주의
// 보이기/숨기기/토글
$("#panel").show();
$("#panel").hide();
$("#panel").toggle();
// 클래스
$("#row1").addClass("active");
$("#row1").removeClass("active");
$("#row1").toggleClass("active");
const on = $("#row1").hasClass("active"); // true/false
// 속성·데이터
$("#link").attr("href", "/next");
const userId = $("#row1").data("userId"); // data-user-id="7" → 7
💡 text()는 이스케이프되지만 html()은 태그가 해석된다 — 사용자 입력을 html()에 넣지 말 것(XSS). data-user-id처럼 하이픈 속성은 data("userId") 카멜로 읽는다.
이벤트 바인딩 · 위임 (동적 요소)
DOM·이벤트 · jQuery
// 문서 준비 후 실행 — 이벤트 바인딩은 이 안에서
$(function () {
// 직접 바인딩 — "지금 존재하는" 요소에만 걸린다
$("#saveBtn").on("click", function () {
console.log("저장 클릭");
});
// ★ 위임 — 나중에 추가된 요소에도 걸린다 (ajax로 그린 행/버튼 필수)
$(document).on("click", ".del-btn", function () {
$(this).closest("tr").remove();
});
// change · keyup
$("#deptSelect").on("change", function () {
console.log("선택값:", $(this).val());
});
$("#keyword").on("keyup", function (e) {
if (e.key === "Enter") console.log("검색:", $(this).val());
});
});
💡 $("셀렉터").on()은 바인딩 시점에 존재하는 요소에만 걸린다 — 동적으로 추가한 버튼이 "클릭이 안 먹는" 원인 1위. 동적 요소는 $(document).on("click", "셀렉터", ...) 위임으로.
테이블 행 동적 추가 · 삭제 (조회 결과 그리기)
DOM·이벤트 · jQuery
// 조회 결과(객체 배열)를 tbody에 그린다
function renderRows(rows) {
const $tbody = $("#resultTable tbody");
$tbody.empty(); // 기존 행 비우기
if (rows.length === 0) {
$tbody.append('<tr><td colspan="3">결과 없음</td></tr>');
return;
}
$.each(rows, function (i, row) {
const $tr = $("<tr>");
$tr.append($("<td>").text(row.id));
$tr.append($("<td>").text(row.name)); // 값은 text()로 — 안전
$tr.append($("<td>").html('<button type="button" class="del-btn">삭제</button>'));
$tbody.append($tr);
});
}
// 행 삭제 — 동적 행이므로 위임 바인딩
$(document).on("click", ".del-btn", function () {
$(this).closest("tr").remove();
});
// 디바운스: 입력이 "멈춘 뒤" 1번만 실행 — 검색 자동완성
function debounce(fn, ms) {
let timer = null;
return function (...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), ms);
};
}
// 스로틀: 연속 발생 중에도 ms당 최대 1번 — 스크롤·리사이즈
function throttle(fn, ms) {
let last = 0;
return function (...args) {
const now = Date.now();
if (now - last < ms) return;
last = now;
fn.apply(this, args);
};
}
const search = debounce((word) => console.log("검색:", word), 300);
document.querySelector("#keyword").addEventListener("input", (e) => {
search(e.target.value);
});
💡 디바운스 = 마지막 한 번(입력이 끝나면), 스로틀 = 주기당 한 번(연속 중에도 간간이). 검색창은 디바운스, 스크롤 위치 계산은 스로틀.
파일 다운로드 만들기 (Blob · CSV 내보내기)
파일·저장
// 화면의 데이터를 파일로 내려주기 — 서버 없이 브라우저에서 생성
function downloadFile(filename, text, mime) {
const blob = new Blob([text], { type: mime });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = filename;
a.click();
URL.revokeObjectURL(url); // 메모리 해제
}
// CSV 내보내기 — 엑셀 한글 깨짐 방지: 맨 앞에 BOM
const rows = [["id", "이름"], [1, "홍길동"], [2, "김철수"]];
const csv = rows.map((r) => r.join(",")).join("\n");
downloadFile("users.csv", "\uFEFF" + csv, "text/csv;charset=utf-8");
💡 엑셀에서 한글이 깨지면 CSV 맨 앞에 BOM(\uFEFF)을 붙인다. 쉼표·줄바꿈이 든 값은 큰따옴표로 감싸고, 값 안의 큰따옴표는 두 번 겹쳐 쓴다.
파일 업로드 (FormData · multipart)
파일·저장 · 표준 fetch
// <input type="file" id="fileInput"> 의 파일을 업로드
async function uploadFile() {
const file = document.querySelector("#fileInput").files[0];
if (!file) {
alert("파일을 선택하세요");
return;
}
const fd = new FormData();
fd.append("file", file);
fd.append("memo", "정산 자료"); // 일반 필드도 함께 전송 가능
const res = await fetch("/api/upload", { method: "POST", body: fd });
if (!res.ok) throw new Error("HTTP " + res.status);
return await res.json();
}
💡 Content-Type을 직접 지정하지 말 것 — 브라우저가 boundary까지 자동으로 붙인다. jQuery $.ajax로 보낼 땐 processData: false, contentType: false 두 개가 필수.