Backend
home

JavaScript 기초와 Vanilla JS — 라이브러리 없이 다 된다

생성일
2026/04/06 04:27
태그
JavaScript
React도 모르고, Vue도 모르고, 그냥 JavaScript만 손에 쥐어진 상태에서 뭔가 만들어보려고 했을 때 제일 막막했던 게 있다.
"jQuery 없으면 못 하는 거 아니야?"
아니다. Vanilla JS — 즉 순수 JavaScript만으로 DOM 조작, 이벤트 처리, 동적 렌더링까지 다 된다. 오히려 기초를 제대로 잡으면 React 같은 라이브러리를 나중에 배울 때 왜 그렇게 만들었는지 이해가 훨씬 빠르다.

변수와 데이터 타입

JavaScript에서 변수를 선언하는 방법은 세 가지다.
var name = '옛날 방식'; // 쓰지 마라 let count = 0; // 재할당 가능 const MAX = 100; // 재할당 불가
JavaScript
복사
var는 함수 스코프라 예상치 못한 동작이 생긴다. 요즘은 거의 안 쓴다. letconst는 블록 스코프라 훨씬 예측하기 쉽다. 기본적으로 const 쓰고, 값이 바뀌어야 할 때만 let 쓰는 게 습관이 된다.
데이터 타입은 크게 이렇다.
const str = '문자열'; // String const num = 42; // Number const bool = true; // Boolean const arr = [1, 2, 3]; // Array const obj = { name: 'Kim' }; // Object const nothing = null; // null let undef; // undefined
JavaScript
복사

함수 선언 방식

JavaScript에서 함수를 쓰는 방법도 여러 가지다.
// 함수 선언식 — 호이스팅됨 function greet(name) { return `안녕, ${name}!`; } // 함수 표현식 const greet = function(name) { return `안녕, ${name}!`; }; // 화살표 함수 — 요즘 가장 많이 씀 const greet = (name) => `안녕, ${name}!`;
JavaScript
복사
화살표 함수는 return이 한 줄이면 중괄호와 return 키워드 생략 가능하다. 콜백 함수에서 특히 자주 쓴다.

DOM 조작

DOM(Document Object Model)은 HTML을 JavaScript로 다룰 수 있게 해주는 구조다. 여기서부터 진짜 동적인 페이지를 만들 수 있게 된다.
// 요소 선택 const btn = document.querySelector('#myBtn'); // ID const items = document.querySelectorAll('.item'); // 클래스 전체 // 내용 변경 const title = document.querySelector('h1'); title.textContent = '바뀐 제목'; title.innerHTML = '<span>HTML도 가능</span>'; // 스타일 변경 title.style.color = 'tomato'; title.style.fontSize = '2rem'; // 클래스 조작 title.classList.add('active'); title.classList.remove('active'); title.classList.toggle('active'); // 있으면 제거, 없으면 추가
JavaScript
복사
querySelector는 CSS 선택자 문법을 그대로 쓴다. 익숙한 문법이라 금방 손에 익는다.

이벤트 처리

버튼 클릭, 입력, 키보드, 마우스 — 모든 사용자 인터랙션은 이벤트로 처리한다.
const btn = document.querySelector('#btn'); btn.addEventListener('click', function(e) { console.log('클릭됨', e.target); }); // 화살표 함수로 btn.addEventListener('click', (e) => { e.preventDefault(); // 기본 동작 막기 (링크 이동, 폼 제출 등) console.log('클릭됨'); });
JavaScript
복사
자주 쓰는 이벤트 종류는 이렇다.
click — 클릭
input — 입력값 변경
submit — 폼 제출
keydown / keyup — 키보드
mouseover / mouseout — 마우스 hover
DOMContentLoaded — HTML 파싱 완료

동적 요소 생성과 삭제

JavaScript로 HTML 요소를 직접 만들고 붙이고 지우는 것도 된다.
// 요소 생성 const newItem = document.createElement('li'); newItem.textContent = '새 항목'; newItem.classList.add('item'); // 부모에 추가 const list = document.querySelector('#list'); list.appendChild(newItem); // 마지막에 추가 list.prepend(newItem); // 맨 앞에 추가 // 삭제 newItem.remove();
JavaScript
복사

배열과 객체 다루기

JavaScript에서 데이터를 다루는 핵심은 배열 메서드다.
const nums = [1, 2, 3, 4, 5]; // map — 변환해서 새 배열 const doubled = nums.map(n => n * 2); // [2, 4, 6, 8, 10] // filter — 조건에 맞는 것만 const evens = nums.filter(n => n % 2 === 0); // [2, 4] // find — 첫 번째 일치하는 것 const found = nums.find(n => n > 3); // 4 // forEach — 반복 실행 nums.forEach(n => console.log(n)); // reduce — 누적 const sum = nums.reduce((acc, cur) => acc + cur, 0); // 15
JavaScript
복사
이 다섯 개만 제대로 써도 대부분의 데이터 처리가 된다.

Vanilla JS가 왜 중요하냐면

React를 배울 때 useState가 왜 존재하는지, 왜 직접 DOM을 건드리지 않는지 — 이게 이해가 안 되면 그냥 외워서 쓰게 된다. Vanilla JS로 직접 DOM을 조작해보면 왜 프레임워크가 그 문제를 해결하려 했는지 자연스럽게 납득이 된다.
기초 없이 프레임워크부터 들어가면 나중에 반드시 벽에 막힌다. 순서가 있다.

실제 구현 코드 전체

JS 기초 개념들을 전부 담은 인터랙티브 페이지다. 변수 타입 확인기, 할 일 목록(CRUD), 실시간 필터, 키보드 이벤트까지 Live Server로 바로 띄울 수 있다.
<!DOCTYPE html> <html lang="ko"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Vanilla JS 기초 데모</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } :root { --primary: #f59e0b; --primary-dark: #d97706; --bg: #0f172a; --surface: #1e293b; --surface2: #334155; --text: #f1f5f9; --sub: #94a3b8; --border: #334155; --radius: 10px; --green: #22c55e; --red: #ef4444; } body { font-family: 'Segoe UI', sans-serif; background: var(--bg); color: var(--text); line-height: 1.7; } /* ── 내비게이션 ── */ header { position: sticky; top: 0; background: var(--surface); border-bottom: 1px solid var(--border); z-index: 100; } nav { max-width: 960px; margin: 0 auto; padding: 0 20px; height: 56px; display: flex; justify-content: space-between; align-items: center; } .logo { font-weight: 800; font-size: 1.1rem; color: var(--primary); } .nav-tabs { display: flex; gap: 6px; list-style: none; } .nav-tabs button { background: none; border: none; color: var(--sub); cursor: pointer; padding: 6px 14px; border-radius: 6px; font-size: 0.88rem; transition: all 0.2s; } .nav-tabs button:hover, .nav-tabs button.active { background: var(--primary); color: #000; font-weight: 600; } /* ── 공통 레이아웃 ── */ main { max-width: 960px; margin: 0 auto; padding: 40px 20px; } .section { display: none; } .section.active { display: block; } .section-title { font-size: 1.4rem; font-weight: 700; margin-bottom: 6px; } .section-desc { color: var(--sub); font-size: 0.9rem; margin-bottom: 28px; } /* ── 카드 ── */ .card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 24px; margin-bottom: 16px; } .card h3 { font-size: 0.95rem; font-weight: 700; margin-bottom: 14px; color: var(--primary); } /* ── 공통 인풋/버튼 ── */ input[type=text], select { width: 100%; background: var(--surface2); border: 1px solid var(--border); border-radius: 8px; color: var(--text); padding: 10px 14px; font-size: 0.9rem; outline: none; transition: border-color 0.2s; } input[type=text]:focus, select:focus { border-color: var(--primary); } .btn { padding: 9px 20px; border: none; border-radius: 8px; cursor: pointer; font-size: 0.88rem; font-weight: 600; transition: opacity 0.2s; } .btn:hover { opacity: 0.85; } .btn-primary { background: var(--primary); color: #000; } .btn-danger { background: var(--red); color: #fff; } .btn-sm { padding: 5px 12px; font-size: 0.8rem; } /* ── 섹션 1: 타입 확인기 ── */ .type-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 12px; margin-top: 16px; } .type-item { background: var(--surface2); border-radius: 8px; padding: 14px 16px; } .type-item .t-label { font-size: 0.75rem; color: var(--sub); margin-bottom: 4px; } .type-item .t-value { font-size: 0.95rem; font-weight: 600; word-break: break-all; } .type-item .t-type { display: inline-block; margin-top: 6px; padding: 2px 8px; border-radius: 999px; font-size: 0.75rem; font-weight: 700; } .badge-string { background: #3b82f620; color: #60a5fa; } .badge-number { background: #22c55e20; color: #4ade80; } .badge-boolean { background: #f59e0b20; color: #fbbf24; } .badge-object { background: #a855f720; color: #c084fc; } .badge-undefined { background: #64748b20; color: #94a3b8; } #typeResult { margin-top: 14px; padding: 14px 16px; background: var(--surface2); border-radius: 8px; font-size: 0.9rem; min-height: 48px; } /* ── 섹션 2: 할 일 목록 ── */ .todo-form { display: flex; gap: 10px; margin-bottom: 16px; } .todo-form input { flex: 1; } #todoFilter { display: flex; gap: 8px; margin-bottom: 14px; } #todoFilter button { background: var(--surface2); border: 1px solid var(--border); border-radius: 6px; color: var(--sub); padding: 5px 14px; font-size: 0.82rem; cursor: pointer; transition: all 0.2s; } #todoFilter button.active { background: var(--primary); border-color: var(--primary); color: #000; font-weight: 600; } #todoList { list-style: none; display: flex; flex-direction: column; gap: 8px; } .todo-item { display: flex; align-items: center; gap: 12px; background: var(--surface2); border-radius: 8px; padding: 12px 16px; transition: opacity 0.2s; } .todo-item.done { opacity: 0.4; } .todo-item.done .todo-text { text-decoration: line-through; } .todo-check { width: 18px; height: 18px; accent-color: var(--primary); cursor: pointer; flex-shrink: 0; } .todo-text { flex: 1; font-size: 0.92rem; } #todoCount { margin-top: 14px; font-size: 0.83rem; color: var(--sub); } /* ── 섹션 3: 이벤트 로그 ── */ .event-box { background: var(--surface2); border-radius: 8px; padding: 20px; margin-bottom: 16px; } .event-box .label { font-size: 0.8rem; color: var(--sub); margin-bottom: 8px; } #eventTarget { width: 100%; height: 90px; display: flex; align-items: center; justify-content: center; border-radius: 8px; background: var(--surface); border: 2px dashed var(--border); font-size: 0.9rem; color: var(--sub); cursor: pointer; user-select: none; transition: all 0.15s; } #eventTarget:hover { border-color: var(--primary); color: var(--primary); } #eventTarget.pressed { background: var(--primary); color: #000; border-style: solid; border-color: var(--primary); } #keyDisplay { text-align: center; font-size: 1.6rem; font-weight: 800; min-height: 48px; color: var(--primary); margin-top: 10px; } #eventLog { list-style: none; display: flex; flex-direction: column; gap: 6px; max-height: 200px; overflow-y: auto; } .log-item { display: flex; align-items: center; gap: 10px; background: var(--surface2); border-radius: 6px; padding: 8px 12px; font-size: 0.83rem; animation: fadeIn 0.2s ease; } @keyframes fadeIn { from { opacity:0; transform:translateY(-4px); } to { opacity:1; transform:none; } } .log-badge { padding: 2px 8px; border-radius: 999px; font-size: 0.72rem; font-weight: 700; flex-shrink: 0; } .log-click { background: #3b82f620; color: #60a5fa; } .log-key { background: #22c55e20; color: #4ade80; } .log-mouse { background: #f59e0b20; color: #fbbf24; } /* ── 섹션 4: 배열 메서드 ── */ .method-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; } @media (max-width: 600px) { .method-grid { grid-template-columns: 1fr; } } .method-card { background: var(--surface2); border-radius: 8px; padding: 16px; } .method-card .m-name { font-size: 0.8rem; font-weight: 700; color: var(--primary); margin-bottom: 6px; } .method-card .m-code { font-family: monospace; font-size: 0.8rem; color: var(--sub); margin-bottom: 10px; line-height: 1.6; } .method-card .m-result { font-size: 0.85rem; color: var(--green); font-weight: 600; } .run-btn-wrap { text-align: center; margin-top: 20px; } </style> </head> <body> <header> <nav> <span class="logo">⚡ Vanilla JS</span> <ul class="nav-tabs"> <li><button class="active" data-tab="tab-types">타입</button></li> <li><button data-tab="tab-todo">할 일</button></li> <li><button data-tab="tab-events">이벤트</button></li> <li><button data-tab="tab-array">배열</button></li> </ul> </nav> </header> <main> <!-- 섹션 1: 데이터 타입 확인기 --> <section class="section active" id="tab-types"> <p class="section-title">데이터 타입 확인기</p> <p class="section-desc">값을 입력하면 JavaScript가 어떤 타입으로 인식하는지 확인한다. typeof 연산자가 어떻게 동작하는지 눈으로 볼 수 있다.</p> <div class="card"> <h3>📌 직접 입력해보기</h3> <div style="display:flex; gap:10px; align-items:center;"> <input type="text" id="typeInput" placeholder="값을 입력하세요 (예: 42, true, &quot;hello&quot;, null)" /> <button class="btn btn-primary" id="typeCheck">확인</button> </div> <div id="typeResult">여기에 결과가 표시됩니다.</div> </div> <div class="card"> <h3>📌 기본 타입 예시</h3> <div class="type-grid" id="typeGrid"></div> </div> </section> <!-- 섹션 2: 할 일 목록 --> <section class="section" id="tab-todo"> <p class="section-title">할 일 목록 (CRUD)</p> <p class="section-desc">createElement, appendChild, remove — DOM 조작 기초를 실제로 써보는 예제다. 추가/완료/삭제/필터가 전부 Vanilla JS로만 구현돼 있다.</p> <div class="card"> <h3>📌 할 일 추가</h3> <div class="todo-form"> <input type="text" id="todoInput" placeholder="할 일을 입력하고 Enter 또는 추가 버튼" /> <button class="btn btn-primary" id="todoAdd">추가</button> </div> <div id="todoFilter"> <button class="active" data-filter="all">전체</button> <button data-filter="active">미완료</button> <button data-filter="done">완료</button> </div> <ul id="todoList"></ul> <p id="todoCount"></p> </div> </section> <!-- 섹션 3: 이벤트 로그 --> <section class="section" id="tab-events"> <p class="section-title">이벤트 처리 데모</p> <p class="section-desc">addEventListener로 클릭, 키보드, 마우스 이벤트를 잡는 예제다. 어떤 이벤트가 어느 타이밍에 발생하는지 로그로 확인할 수 있다.</p> <div class="card"> <h3>📌 클릭 / 마우스 이벤트</h3> <div class="event-box"> <p class="label">아래 박스를 클릭하거나 마우스를 올려보자</p> <div id="eventTarget">여기를 클릭하거나 마우스를 올려보자</div> </div> </div> <div class="card"> <h3>📌 키보드 이벤트</h3> <div class="event-box"> <p class="label">아무 키나 눌러보자 (이 페이지에 포커스된 상태에서)</p> <div id="keyDisplay"></div> </div> </div> <div class="card"> <h3>📌 이벤트 로그</h3> <ul id="eventLog"></ul> </div> </section> <!-- 섹션 4: 배열 메서드 --> <section class="section" id="tab-array"> <p class="section-title">배열 메서드 시각화</p> <p class="section-desc">map, filter, find, reduce, forEach — 각각이 어떤 값을 돌려주는지 버튼 한 번으로 확인한다.</p> <div class="card"> <h3>📌 원본 배열</h3> <p style="font-family:monospace; font-size:0.95rem; color:var(--primary); margin-bottom:20px;"> const nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; </p> <div class="method-grid" id="methodGrid"></div> <div class="run-btn-wrap"> <button class="btn btn-primary" id="runAll">▶ 전체 실행</button> </div> </div> </section> </main> <script> // ══════════════════════════════════════════ // 1. 탭 네비게이션 // ══════════════════════════════════════════ const tabBtns = document.querySelectorAll('.nav-tabs button'); const sections = document.querySelectorAll('.section'); tabBtns.forEach(btn => { btn.addEventListener('click', () => { // 모든 탭 비활성화 tabBtns.forEach(b => b.classList.remove('active')); sections.forEach(s => s.classList.remove('active')); // 클릭한 탭 활성화 btn.classList.add('active'); document.getElementById(btn.dataset.tab).classList.add('active'); }); }); // ══════════════════════════════════════════ // 2. 데이터 타입 확인기 // ══════════════════════════════════════════ const typeExamples = [ { label: '문자열', value: '"hello"', display: 'hello', type: 'string' }, { label: '숫자', value: '42', display: '42', type: 'number' }, { label: '소수', value: '3.14', display: '3.14', type: 'number' }, { label: '불리언 참', value: 'true', display: 'true', type: 'boolean' }, { label: '불리언 거짓',value: 'false', display: 'false', type: 'boolean' }, { label: 'null', value: 'null', display: 'null', type: 'object' }, { label: 'undefined', value: 'undefined', display: 'undefined', type: 'undefined' }, { label: '배열', value: '[1,2,3]', display: '[1,2,3]', type: 'object' }, { label: '객체', value: '{a:1}', display: '{a:1}', type: 'object' }, ]; const typeGrid = document.getElementById('typeGrid'); typeExamples.forEach(ex => { const item = document.createElement('div'); item.className = 'type-item'; item.innerHTML = ` <div class="t-label">${ex.label}</div> <div class="t-value">${ex.display}</div> <span class="t-type badge-${ex.type}">typeof → "${ex.type}"</span> `; typeGrid.appendChild(item); }); // 직접 입력 타입 확인 const typeInput = document.getElementById('typeInput'); const typeCheck = document.getElementById('typeCheck'); const typeResult = document.getElementById('typeResult'); function checkType() { const raw = typeInput.value.trim(); if (!raw) { typeResult.textContent = '값을 입력해주세요.'; return; } let evaled, typeName; try { evaled = eval(raw); typeName = typeof evaled; } catch(e) { typeName = 'error'; evaled = e.message; } typeResult.innerHTML = ` <span style="color:var(--sub)">입력: </span><strong style="color:var(--text)">${raw}</strong> &nbsp;→&nbsp; <span style="color:var(--sub)">typeof: </span><strong style="color:var(--primary)">&quot;${typeName}&quot;</strong> &nbsp;|&nbsp; <span style="color:var(--sub)">값: </span><strong style="color:var(--green)">${String(evaled)}</strong> `; } typeCheck.addEventListener('click', checkType); typeInput.addEventListener('keydown', e => { if (e.key === 'Enter') checkType(); }); // ══════════════════════════════════════════ // 3. 할 일 목록 // ══════════════════════════════════════════ let todos = []; let filter = 'all'; const todoInput = document.getElementById('todoInput'); const todoAdd = document.getElementById('todoAdd'); const todoList = document.getElementById('todoList'); const todoCount = document.getElementById('todoCount'); function renderTodos() { todoList.innerHTML = ''; const filtered = todos.filter(t => { if (filter === 'active') return !t.done; if (filter === 'done') return t.done; return true; }); if (filtered.length === 0) { todoList.innerHTML = '<li style="color:var(--sub); font-size:0.88rem; padding:10px 0;">할 일이 없다.</li>'; } filtered.forEach(t => { const li = document.createElement('li'); li.className = 'todo-item' + (t.done ? ' done' : ''); li.innerHTML = ` <input type="checkbox" class="todo-check" ${t.done ? 'checked' : ''} data-id="${t.id}" /> <span class="todo-text">${t.text}</span> <button class="btn btn-danger btn-sm" data-del="${t.id}">삭제</button> `; todoList.appendChild(li); }); const done = todos.filter(t => t.done).length; todoCount.textContent = `전체 ${todos.length}개 · 완료 ${done}개 · 미완료 ${todos.length - done}`; } function addTodo() { const text = todoInput.value.trim(); if (!text) return; todos.push({ id: Date.now(), text, done: false }); todoInput.value = ''; renderTodos(); } todoAdd.addEventListener('click', addTodo); todoInput.addEventListener('keydown', e => { if (e.key === 'Enter') addTodo(); }); // 체크박스 & 삭제 — 이벤트 위임 todoList.addEventListener('click', e => { const id = Number(e.target.dataset.id || e.target.dataset.del); if (e.target.dataset.id !== undefined) { const t = todos.find(t => t.id === id); if (t) t.done = !t.done; } if (e.target.dataset.del !== undefined) { todos = todos.filter(t => t.id !== id); } renderTodos(); }); // 필터 버튼 document.querySelectorAll('#todoFilter button').forEach(btn => { btn.addEventListener('click', () => { document.querySelectorAll('#todoFilter button').forEach(b => b.classList.remove('active')); btn.classList.add('active'); filter = btn.dataset.filter; renderTodos(); }); }); renderTodos(); // ══════════════════════════════════════════ // 4. 이벤트 로그 // ══════════════════════════════════════════ const eventTarget = document.getElementById('eventTarget'); const keyDisplay = document.getElementById('keyDisplay'); const eventLog = document.getElementById('eventLog'); function addLog(type, message) { const li = document.createElement('li'); li.className = 'log-item'; li.innerHTML = `<span class="log-badge log-${type}">${type}</span><span>${message}</span>`; eventLog.prepend(li); // 최대 20개 if (eventLog.children.length > 20) eventLog.lastElementChild.remove(); } eventTarget.addEventListener('click', () => { addLog('click', '클릭 이벤트 발생'); }); eventTarget.addEventListener('mousedown', () => { eventTarget.classList.add('pressed'); }); eventTarget.addEventListener('mouseup', () => { eventTarget.classList.remove('pressed'); }); eventTarget.addEventListener('mouseover', () => { addLog('mouse', 'mouseover — 마우스가 박스 안으로 들어왔다'); }); eventTarget.addEventListener('mouseout', () => { addLog('mouse', 'mouseout — 마우스가 박스 밖으로 나갔다'); }); document.addEventListener('keydown', e => { if (['INPUT', 'TEXTAREA'].includes(document.activeElement.tagName)) return; keyDisplay.textContent = e.key === ' ' ? 'Space' : e.key; addLog('key', `keydown → "${e.key === ' ' ? 'Space' : e.key}"`); }); // ══════════════════════════════════════════ // 5. 배열 메서드 시각화 // ══════════════════════════════════════════ const nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; const methods = [ { name: 'map', code: 'nums.map(n => n * 2)', desc: '모든 요소를 2배로 변환', run: () => nums.map(n => n * 2), }, { name: 'filter', code: 'nums.filter(n => n % 2 === 0)', desc: '짝수만 남긴다', run: () => nums.filter(n => n % 2 === 0), }, { name: 'find', code: 'nums.find(n => n > 6)', desc: '6보다 큰 첫 번째 값', run: () => nums.find(n => n > 6), }, { name: 'reduce', code: 'nums.reduce((acc, cur) => acc + cur, 0)', desc: '전체 합산', run: () => nums.reduce((acc, cur) => acc + cur, 0), }, { name: 'every', code: 'nums.every(n => n > 0)', desc: '모두 0보다 큰가?', run: () => nums.every(n => n > 0), }, { name: 'some', code: 'nums.some(n => n > 8)', desc: '8보다 큰 게 하나라도 있나?', run: () => nums.some(n => n > 8), }, { name: 'includes', code: 'nums.includes(5)', desc: '5가 포함돼 있나?', run: () => nums.includes(5), }, { name: 'slice', code: 'nums.slice(2, 5)', desc: '인덱스 2~4 잘라내기', run: () => nums.slice(2, 5), }, ]; const methodGrid = document.getElementById('methodGrid'); methods.forEach(m => { const card = document.createElement('div'); card.className = 'method-card'; card.innerHTML = ` <div class="m-name">.${m.name}()</div> <div class="m-code">${m.code}</div> <div class="m-desc" style="font-size:0.82rem; color:var(--sub); margin-bottom:8px;">${m.desc}</div> <div class="m-result" id="result-${m.name}">— 실행 전 —</div> `; methodGrid.appendChild(card); }); document.getElementById('runAll').addEventListener('click', () => { methods.forEach(m => { const result = m.run(); const el = document.getElementById(`result-${m.name}`); el.textContent = '→ ' + JSON.stringify(result); }); }); </script> </body> </html>
HTML
복사