처음 배우는 분을 위한 단계별 안내 · 모든 섹션에 ▶ 실행 버튼과 직접 해볼 수 있는 데모가 있습니다
웹 페이지는 보통 3가지 언어로 만들어집니다. 각자 역할이 분명히 다릅니다. 한 마디로 정리하면 HTML은 뼈대, CSS는 옷, JS는 행동입니다.
구조(content)
무엇이 들어 있는가?
제목, 문단, 이미지, 버튼, 표 같은 요소를 배치합니다.
모양(style)
어떻게 보이는가?
색, 글꼴, 여백, 크기, 애니메이션 같은 꾸밈을 담당합니다.
동작(behavior)
무엇을 하는가?
클릭 반응, 데이터 처리, 화면 갱신 같은 인터랙션을 만듭니다.
하나의 .html 파일 안에 셋이 함께 살 수 있습니다. <style> 태그
안엔 CSS, <script> 태그 안엔 JS가 들어갑니다.
<!DOCTYPE html>
<html>
<head>
<!-- 🎨 CSS: 어떻게 보일까 -->
<style>
#msg { color: tomato; font-size: 20px; }
button { padding: 8px 16px; }
</style>
</head>
<body>
<!-- 📄 HTML: 무엇이 있는가 -->
<p id="msg">안녕하세요!</p>
<button id="btn">클릭</button>
<!-- ⚙️ JS: 무엇을 할까 -->
<script>
document.querySelector('#btn').addEventListener('click', () => {
document.querySelector('#msg').textContent = '버튼이 눌렸어요!';
});
</script>
</body>
</html>
↑ 이 한 페이지에서 셋이 협업합니다.
• HTML이 <p>와 <button>을 만들고
• CSS가 글자색을 토마토색으로 칠하고
• JS가 버튼 클릭에 반응해 문구를 바꿉니다.
<script> 태그 안에 직접 적기.app.js 파일을 만들고 <script src="app.js" defer></script>로 불러오기. (실무 표준)<script> 위치는 왜 중요한가요?
브라우저는 HTML을 위에서 아래로 한 줄씩 읽습니다. JS가 HTML 요소를 찾으려는데
그 요소가 아직 안 만들어졌다면 null이 돌아옵니다.
그래서 <script>는 보통 </body> 바로 위에 두거나, defer 속성을 붙여
"HTML을 다 읽은 다음에 실행해 줘"라고 알려줍니다.
| 상황 | JS의 역할 |
|---|---|
| 버튼 클릭, 폼 제출 | 사용자 행동에 반응 |
| 입력값 검증 | "이메일 형식이 맞나?" 검사 |
| 화면 일부만 갱신 | 새로고침 없이 내용 바꾸기 |
| 외부 데이터 불러오기 | fetch로 JSON·CSV 받아오기 |
| 차트·지도·애니메이션 | 시각화 라이브러리 구동 |
변수(variable)는 값을 잠시 보관해 두는 이름이 붙은 상자입니다. 나중에 그 이름을 부르면 안에 들어 있는 값이 꺼내져 나옵니다.
같은 값을 여러 곳에서 쓸 때, 매번 다시 적는 대신 한 번 저장해 두고 이름으로 부르기 위해서입니다. 값이 바뀌면 한 곳만 고치면 되니 실수도 줄어듭니다.
| 키워드 | 재할당 | 언제 쓰나요? |
|---|---|---|
const | ❌ 못 함 | 기본값으로 쓰세요. 값을 바꿀 일이 없을 때 |
let | ✅ 가능 | 값이 중간에 바뀌어야 할 때만 |
var | ✅ 가능 | ❌ 쓰지 마세요. 옛날 방식이라 버그 위험 |
// ── const: 한번 정한 값을 바꿀 수 없습니다 ──
const 이름 = "홍길동";
const 나이 = 25;
console.log(이름, 나이); // 홍길동 25
// 아래 줄은 에러! const는 다시 대입할 수 없어요
// 이름 = "이몽룡"; ❌ TypeError
// ── let: 값을 바꿔야 할 때 ──
let 점수 = 80;
점수 = 90; // ✅ OK
점수 = 점수 + 5; // ✅ 95
console.log("점수:", 점수);
• 영문자, 숫자, _, $만 사용 (한글도 가능하지만 보통 영문)
• 숫자로 시작하면 안 됨 (1name ❌)
• 예약어는 못 씀 (let, if, function 등)
• 관습적으로 카멜표기법을 씁니다: userName, totalCount
const로 만든 변수에 다시 대입하기 → "Assignment to constant variable" 에러이름 = "민수" → 의도치 않은 전역 변수 생성
const 이름 = "민수";
const 나이 = 20;
// 방법 1: + 로 잇기
console.log("저는 " + 이름 + "이고, " + 나이 + "살입니다.");
// 방법 2: 백틱(`)으로 감싸고 ${변수} ← 이게 훨씬 편합니다!
console.log(`저는 ${이름}이고, ${나이}살입니다.`);
// 계산도 안에서 가능
console.log(`내년엔 ${나이 + 1}살이 됩니다.`);
const, 값이 바뀔 때만 let"변수에 담는 값의 종류를 자료형이라 합니다. 종류에 따라 할 수 있는 일이 달라요. (숫자는 더할 수 있지만 글자는 직접 곱할 수 없죠.)
| 이름 | 예시 | 설명 |
|---|---|---|
| string (문자열) | "안녕" 'hi' | 글자 — 따옴표로 감쌈 |
| number (숫자) | 42 3.14 | 정수·소수 모두 같은 number |
| boolean (참/거짓) | true false | 예/아니오 두 가지 |
| null | null | "비어 있음"을 일부러 표시 |
| undefined | undefined | 아직 값이 안 정해짐 |
| array (배열) | [1, 2, 3] | 여러 값을 순서대로 |
| object (객체) | {이름:"민수"} | 키–값 쌍의 묶음 |
// typeof 로 자료형을 확인할 수 있어요
console.log(typeof "사과"); // "string"
console.log(typeof 100); // "number"
console.log(typeof true); // "boolean"
console.log(typeof undefined); // "undefined"
// 배열과 객체
const 과일들 = ["사과", "바나나", "포도"]; // 배열
console.log(과일들[0]); // "사과" (0부터 셈!)
console.log(과일들.length); // 3
const 학생 = { 이름: "민수", 나이: 20 }; // 객체
console.log(학생.이름); // "민수"
console.log(학생["나이"]); // 20
console.log("5" + 3); // "53" ← 문자열 결합!
console.log("5" - 3); // 2 ← 숫자로 자동 변환
console.log(Number("5")); // 5
console.log(String(5)); // "5"
[1]이 아니라 [0]입니다. 컴퓨터는 0부터 셉니다!undefined: 아직 값을 안 줬다 (시스템 기본)null: 일부러 "비어 있음"을 표시했다 (개발자 의도)
함수(function)는 여러 줄의 코드를 이름 하나로 묶어 둔 것입니다. 한 번 만들어 두면 필요할 때마다 그 이름만 부르면 됩니다.
// function 이름(매개변수) { ...할 일... return 결과; }
function 인사하기(이름) {
return `안녕하세요, ${이름}님!`;
}
// 함수를 부르려면: 이름() 형태
console.log(인사하기("민수")); // "안녕하세요, 민수님!"
console.log(인사하기("영희")); // "안녕하세요, 영희님!"
용어 정리:
• 매개변수(parameter): 함수가 받을 값의 이름 — 위에선 이름
• 인자(argument): 부를 때 실제로 넣는 값 — 위에선 "민수"
• return: 함수가 돌려주는 결과 — 없으면 undefined 반환
// 위와 똑같은 함수를 화살표로 ↓
const 인사하기2 = (이름) => {
return `안녕하세요, ${이름}님!`;
};
// 한 줄짜리 함수는 더 짧게 (return 생략 가능)
const 두배 = (n) => n * 2;
console.log(두배(5)); // 10
// 매개변수 여러 개
const 더하기 = (a, b) => a + b;
console.log(더하기(3, 7)); // 10
// 매개변수 없음
const 인사 = () => "안녕!";
console.log(인사());
// 인자를 안 넣으면 기본값이 사용됩니다
function 음료주문(메뉴 = "아메리카노", 사이즈 = "Tall") {
return `${사이즈} ${메뉴} 주문 완료!`;
}
console.log(음료주문()); // Tall 아메리카노 주문 완료!
console.log(음료주문("라떼")); // Tall 라떼 주문 완료!
console.log(음료주문("라떼", "Grande")); // Grande 라떼 주문 완료!
return으로 결과를 돌려줌 (계산기 같은 함수)return 없이 화면만 바꾸거나 콘솔에 출력 (액션 함수)
프로그램이 상황에 따라 다른 길로 가도록 만드는 문법입니다. "비가 오면 우산을 챙기고, 안 오면 그냥 나간다" — 이런 흐름이죠.
| 기호 | 의미 | 예시 |
|---|---|---|
=== | 같다 (자료형까지) | 5 === 5 → true |
!== | 다르다 | 5 !== 3 → true |
> < | 크다 / 작다 | 10 > 3 → true |
>= <= | 크거나 같다 / 작거나 같다 | 5 >= 5 → true |
&& | 그리고 (둘 다 true) | a > 0 && a < 10 |
|| | 또는 (하나라도 true) | a === 1 || a === 2 |
! | 반대 (true ↔ false) | !true → false |
=는 "대입"이고 ===가 "비교"입니다. 비교할 땐 항상 ===를 쓰세요!const 점수 = 78;
if (점수 >= 90) {
console.log("A 학점");
} else if (점수 >= 80) {
console.log("B 학점");
} else if (점수 >= 70) {
console.log("C 학점"); // ← 78점이라 여기 실행!
} else {
console.log("재시험");
}
// AND, OR 연산자와 함께
const 나이 = 17;
const 보호자동반 = true;
if (나이 >= 19 || 보호자동반) {
console.log("입장 가능");
} else {
console.log("입장 불가");
}
// 형태: 조건 ? 참일때값 : 거짓일때값 const 나이 = 20; const 결과 = 나이 >= 19 ? "성인" : "미성년자"; console.log(결과); // "성인" // 위는 아래와 똑같음 let 결과2; if (나이 >= 19) 결과2 = "성인"; else 결과2 = "미성년자";
const 요일 = "월";
switch (요일) {
case "월":
case "화":
case "수":
case "목":
case "금":
console.log("평일");
break;
case "토":
case "일":
console.log("주말");
break;
default:
console.log("알 수 없는 요일");
}
switch에서 break를 빼먹으면 다음 case로 넘어가 버립니다. 꼭 넣어 주세요.같은 일을 여러 번 해야 할 때 사용합니다. 100명에게 인사하기, 리스트의 모든 항목 출력하기 — 손으로 100번 쓸 순 없겠죠?
// for (시작; 조건; 증가) { ...반복할 일... }
for (let i = 0; i < 5; i++) {
console.log(`${i}번째 반복`);
}
// 출력: 0번째 반복 / 1번째 반복 / ... / 4번째 반복
// 1부터 10까지의 합
let 합계 = 0;
for (let i = 1; i <= 10; i++) {
합계 = 합계 + i; // 합계 += i 와 같음
}
console.log("1~10 합:", 합계); // 55
구조를 차근차근 보면:
• let i = 0 → 시작값 (보통 0)
• i < 5 → 이 조건이 true인 동안 반복
• i++ → 한 번 끝날 때마다 i를 1 증가 (i = i + 1)
const 과일들 = ["사과", "바나나", "포도", "키위"];
for (const 과일 of 과일들) {
console.log("좋아하는 과일:", 과일);
}
// 출력: 사과 → 바나나 → 포도 → 키위 차례로
const 점수목록 = [85, 92, 78, 95, 60];
점수목록.forEach((점수, 인덱스) => {
console.log(`${인덱스 + 1}번 학생: ${점수}점`);
});
// 횟수가 정해지지 않고 "어떤 조건이 만족될 때까지"
let 남은횟수 = 3;
while (남은횟수 > 0) {
console.log(`남은 기회: ${남은횟수}`);
남은횟수--; // 1씩 감소 (남은횟수 = 남은횟수 - 1)
}
console.log("게임 끝!");
// break: 반복 즉시 종료
for (let i = 0; i < 10; i++) {
if (i === 5) break; // 5가 되면 멈춤
console.log(i); // 0, 1, 2, 3, 4
}
// continue: 이번 회차만 건너뛰고 다음으로
for (let i = 0; i < 5; i++) {
if (i === 2) continue; // 2일 때 console.log를 건너뜀
console.log(i); // 0, 1, 3, 4
}
for, 배열을 처음부터 끝까지 돌면 for...of, 조건만 있다면 while.DOM(Document Object Model)은 브라우저가 HTML을 읽어 만든 객체 트리입니다. JS로 이 트리의 어떤 요소든 골라내서 글자, 모양, 행동을 바꿀 수 있어요. "인터랙티브한 웹"의 진짜 출발점입니다.
document가 뿌리, <html>이 줄기, <body> 안의 모든 요소가 잎사귀입니다.
JS는 그 나무의 어떤 잎이든 찾아가서 만질 수 있는 도구입니다.
querySelector) → ② 바꾸기 (textContent, style 등) → ③ 반응 등록 (addEventListener)
// 1. ID로 찾기 (한 개) — 가장 빠름
const 제목 = document.getElementById('title');
// 2. CSS 선택자로 찾기 (한 개) — 가장 자주 씀
const 첫번째버튼 = document.querySelector('.btn'); // 클래스
const 입력창 = document.querySelector('#email'); // id
const 첫p = document.querySelector('p'); // 태그
// 3. CSS 선택자로 여러 개 찾기 → 배열 비슷한 것
const 모든버튼 = document.querySelectorAll('.btn');
모든버튼.forEach(btn => console.log(btn.textContent));
// 4. 태그명으로 여러 개
const 모든p = document.getElementsByTagName('p');
| 메서드 | 반환 | 언제 |
|---|---|---|
getElementById('id') | 요소 1개 | id 알 때 가장 빠름 |
querySelector('CSS') | 맞는 첫 번째 1개 | 가장 만능 |
querySelectorAll('CSS') | 맞는 모든 요소 | 여러 개 다룰 때 |
아래는 위 코드의 대상이 되는 실제 HTML 구조입니다. ▶ 실행을 누르면 위 코드가 그대로 동작해 어떤 요소가 잡혔는지 결과로 보여줍니다.
첫 번째 문단입니다.
두 번째 문단입니다.
const 제목 = document.querySelector('#target-text');
// textContent: 글자만 (HTML 태그 안 먹힘 → 안전)
제목.textContent = "새 제목입니다";
// innerHTML: HTML 태그 먹힘 (강력하지만 보안 주의)
제목.innerHTML = "<b>굵은</b> 제목";
// 입력창의 값은 .value
const 입력 = document.querySelector('#user-input');
console.log(입력.value); // 사용자가 입력한 값 읽기
입력.value = "기본값 세팅"; // 값 채워 넣기
innerHTML에 사용자가 입력한 값을 그대로 넣으면 XSS 공격 위험! 글자만 보여줄 땐 항상 textContent를 쓰세요.
아래 데모는 위 코드 블록과 같은 식별자(#target-text, #user-input)를 사용합니다.
버튼을 누르면 위에 적힌 코드가 한 줄도 바꾸지 않고 그대로 실행됩니다.
// ↑ 위 데모 버튼이 실행하는 코드 (코드와 데모가 1:1 일치)
function changeText() {
document.querySelector('#target-text').textContent
= document.querySelector('#user-input').value;
}
function changeHTML() {
document.querySelector('#target-text').innerHTML
= document.querySelector('#user-input').value;
}
function readValue() {
console.log(document.querySelector('#user-input').value);
}
function setValue() {
document.querySelector('#user-input').value = "기본값 세팅";
}
<b>굵게</b>를 적고 두 버튼을 차례로 눌러 보세요.
textContent는 그대로 글자로 보이고, innerHTML은 진짜 굵게 표시됩니다.
const 박스 = document.querySelector('#color-box');
// CSS의 background-color → JS는 backgroundColor (카멜표기)
박스.style.backgroundColor = "tomato";
박스.style.color = "white";
박스.style.fontSize = "20px";
박스.style.padding = "20px";
박스.style.borderRadius = "12px";
⚠️ CSS 속성에 하이픈(-)이 있으면 JS에선 카멜표기로 바뀝니다.
background-color → backgroundColor,
font-size → fontSize
// ↑ 위 버튼이 실행하는 코드 (위쪽 코드와 한 줄도 다르지 않음)
function applyStyleCode() {
const 박스 = document.querySelector('#color-box');
박스.style.backgroundColor = "tomato";
박스.style.color = "white";
박스.style.fontSize = "20px";
박스.style.padding = "20px";
박스.style.borderRadius = "12px";
}
스타일을 직접 하나씩 바꾸는 것보다 CSS에 클래스를 미리 정의해 두고 JS로 클래스를 붙였다 떼는 방식이 훨씬 깔끔합니다.
// CSS에 .highlight { ... } 가 미리 정의돼 있다고 가정
const 박스 = document.querySelector('#color-box');
박스.classList.add('highlight'); // 클래스 추가
박스.classList.remove('highlight'); // 제거
박스.classList.toggle('highlight'); // 있으면 빼고, 없으면 더하기 (스위치!)
박스.classList.contains('highlight'); // 있나? → true/false (콘솔 출력)
같은 #color-box에 위 4가지 메서드를 그대로 호출해 봅니다.
contains 결과는 아래 출력 박스에 표시됩니다.
const 링크 = document.querySelector('#demo-link');
const 이미지 = document.querySelector('#demo-img');
// 읽기
console.log(링크.getAttribute('href'));
// 쓰기
링크.setAttribute('href', 'https://google.com');
이미지.setAttribute('alt', '귀여운 고양이');
// 자주 쓰는 속성은 직접 접근도 가능
링크.href = 'https://naver.com';
// 삭제
링크.removeAttribute('target');
.style,
여러 속성을 한 번에 바꾸거나 on/off 토글은 .classList가 깔끔합니다.
// 1. 새 요소 만들기
const 새항목 = document.createElement('li');
// 2. 내용/스타일/클래스 설정
새항목.textContent = '새로 추가한 항목';
// 3. 부모 요소 안에 넣기
const 목록 = document.querySelector('#demo-list');
목록.appendChild(새항목); // 맨 끝에 추가
// 목록.prepend(새항목); // 맨 앞에
const 삭제할것 = document.querySelector('#demo-list li:last-child');
삭제할것.remove(); // 자기 자신을 제거 (간단!)
아래는 위 코드의 대상 #demo-list입니다. 두 버튼을 누르면 위 코드 그대로 실행됩니다.
클릭, 키 입력, 마우스 움직임, 스크롤... 사용자의 모든 행동은 이벤트로 전달됩니다. JS는 "이 요소에서 이런 일이 일어나면, 이걸 해라"라고 미리 등록해 둡니다.
const 버튼 = document.querySelector('#evt-btn');
// 형태: 요소.addEventListener('이벤트이름', 실행할함수)
버튼.addEventListener('click', () => {
console.log('버튼이 클릭되었어요!');
});
// 입력창의 글자가 바뀔 때마다
const 입력 = document.querySelector('#evt-input');
입력.addEventListener('input', (e) => {
console.log('현재 값:', e.target.value);
});
// 셀렉트의 선택값이 바뀌면
const 셀렉트 = document.querySelector('#evt-select');
셀렉트.addEventListener('change', (e) => {
console.log('선택:', e.target.value);
});
| 이벤트 | 언제 발생? |
|---|---|
click | 클릭 |
input | 입력창에 글자가 바뀔 때마다 |
change | 입력 끝났을 때 / select 선택 바뀜 |
submit | 폼 제출할 때 |
mouseover / mouseout | 마우스 올릴 때 / 뗄 때 |
keydown | 키보드 누를 때 |
아래 요소들의 식별자(#evt-btn, #evt-input, #evt-select)는
위 코드와 같습니다. ▶ 실행을 누르면 위 코드 3개가 그대로 등록되고,
그 뒤로 발생한 이벤트가 출력 박스에 기록됩니다.
💡 입력창에서 Enter를 눌러도 추가됩니다 (keydown 이벤트로 구현됨)
// 위 데모의 핵심 코드
function addTodo() {
const 입력 = document.querySelector('#todo-input');
const 글자 = 입력.value.trim();
if (글자 === '') return; // 빈 값이면 무시
const li = document.createElement('li');
li.textContent = 글자 + ' ';
const btn = document.createElement('button');
btn.textContent = '삭제';
btn.onclick = () => li.remove(); // 클릭하면 자기 li 제거
li.appendChild(btn);
document.querySelector('#todo-list').appendChild(li);
입력.value = ''; // 입력창 비우기
}
// Enter 키로도 추가
document.querySelector('#todo-input')
.addEventListener('keydown', (e) => {
if (e.key === 'Enter') addTodo();
});
querySelector) → ② 이벤트 등록 (addEventListener)
→ ③ 함수 안에서 값 읽고 → ④ DOM 변경 (textContent, appendChild, remove 등)
JS의 진짜 힘은 전 세계 개발자들이 만들어 둔 패키지(라이브러리)를 한 줄로 끌어다 쓸 수 있다는 점입니다. 차트, 지도, 애니메이션, 데이터 분석… 직접 만들면 몇 달 걸릴 기능을 몇 줄로 끝낼 수 있어요.
<!-- HTML의 <head> 또는 <body> 끝에 추가 -->
<script src="https://cdn.jsdelivr.net/npm/패키지이름"></script>
<!-- 그 다음부터 그 라이브러리의 기능을 그대로 쓸 수 있습니다 -->
<script>
// 라이브러리가 제공하는 객체/함수 사용
</script>
| 분야 | 대표 패키지 (예시) |
|---|---|
| 차트·시각화 | Chart.js, D3.js, ECharts, Plotly |
| 지도 | Leaflet, Mapbox |
| 데이터 처리 | PapaParse(CSV), Lodash, Day.js |
| 애니메이션·UI | GSAP, Anime.js, Swiper |
chart.js@4.4.0). 그래야
나중에 라이브러리가 업데이트돼도 코드가 깨지지 않습니다.