WeCanDo 배너
MalgnSoft 배너

⚡ JavaScript 기초 강의

기초부터 고급까지 단계별로 JavaScript 기초를 다지세요!

1

JavaScript란?

JavaScript의 기본 개념과 특징을 알아봅니다

📊
2

변수와 자료형

변수 선언과 다양한 자료형을 학습합니다

3

연산자

산술, 비교, 논리 연산자를 다룹니다

🔀
4

조건문

if, else, switch를 사용한 조건 분기

🔁
5

반복문

for, while을 이용한 반복 처리

⚙️
6

함수

함수 정의와 호출 방법을 배웁니다

📋
7

배열

배열 생성, 조작, 메서드를 학습합니다

📖
8

객체

객체 생성과 속성 접근 방법

📝
9

문자열 처리

문자열 메서드와 템플릿 리터럴

🌳
10

DOM 조작

HTML 요소 선택과 조작

🖱️
11

이벤트 처리

클릭, 입력 등 이벤트 처리 방법

🏗️
12

클래스와 객체

ES6 클래스 문법

🔮
13

Promise

비동기 처리와 Promise

14

async/await

비동기 코드를 동기처럼 작성

🔄
15

배열 메서드

map, filter, reduce 활용

📦
16

모듈

ES6 모듈 import/export

🔗
17

프로토타입과 상속

JavaScript의 프로토타입 체인과 상속

🎯
18

디스트럭처링과 스프레드

ES6의 구조 분해와 확장 연산자

🔒
19

클로저와 스코프

변수 스코프와 클로저 활용

🌀
20

제너레이터와 이터레이터

반복 가능한 객체와 제너레이터 함수

🚀
21

ES2020+ 최신 문법

Optional Chaining, Nullish Coalescing 등 최신 기능

🔍
22

정규표현식 (RegExp)

패턴 매칭과 문자열 검색/치환

🌐
23

Fetch API와 HTTP 통신

서버와의 비동기 통신과 REST API 활용

💾
24

로컬 스토리지와 세션 스토리지

브라우저에 데이터 저장하기

🧩
25

웹 컴포넌트

Custom Elements와 Shadow DOM

')); // <script>alert("XSS")</script>

5. 실전 활용 예제

Optional Chaining과 Nullish Coalescing을 함께 사용하는 예제입니다:

// 안전하게 중첩 객체 값 가져오기
function safeGet(obj, path, defaultValue) {
    // 방법 1: Optional Chaining 직접 사용
    const value = obj?.level1?.level2?.level3 ?? defaultValue;
    return value;
    
    // 방법 2: 동적 경로
    const keys = path.split('.');
    let result = obj;
    for (const key of keys) {
        result = result?.[key];
    }
    return result ?? defaultValue;
}

// 사용 예시
const data = {
    user: {
        profile: {
            name: 'Alice'
        }
    }
};

console.log(safeGet(data, 'user.profile.name', '이름 없음'));
// 'Alice'

console.log(safeGet(data, 'user.profile.age', 0));
// 0

console.log(safeGet(null, 'user.profile.name', '이름 없음'));
// '이름 없음'

6. 브라우저 호환성

`, problem: "Optional Chaining과 Nullish Coalescing을 사용하여 안전하게 중첩 객체의 값을 가져오는 함수를 작성하세요. obj?.level1?.level2 ?? '기본값' 형식으로 작성하세요.", solution: "function safeGet(obj) {\n return obj?.level1?.level2 ?? '기본값';\n}\n\n// 또는\nconst value = obj?.level1?.level2 ?? '기본값';", test: (code) => { try { return code.includes('?.') && code.includes('??'); } catch (e) { return false; } } }, { title: "정규표현식 (RegExp)", content: `

정규표현식이란?

정규표현식(Regular Expression, RegExp)은 문자열의 패턴을 표현하는 강력한 도구입니다. 특정 패턴을 가진 문자열을 검색, 추출, 치환할 때 사용합니다. JavaScript에서는 RegExp 객체나 리터럴 형식(/pattern/)으로 사용할 수 있습니다.

정규표현식 기본 문법

// 리터럴 형식
const pattern1 = /abc/;

// RegExp 생성자
const pattern2 = new RegExp('abc');

// 플래그 사용
const pattern3 = /abc/gi;  // g: 전역 검색, i: 대소문자 무시

주요 메타 문자

패턴 의미 예시
. 임의의 문자 하나 /a.c/ → "abc", "a1c"
^ 문자열의 시작 /^abc/ → "abc"로 시작
$ 문자열의 끝 /abc$/ → "abc"로 끝
* 0개 이상 반복 /ab*c/ → "ac", "abc", "abbc"
+ 1개 이상 반복 /ab+c/ → "abc", "abbc" (단, "ac"는 아님)
? 0개 또는 1개 /ab?c/ → "ac", "abc"
\\d 숫자 (0-9) /\\d+/ → 하나 이상의 숫자
\\w 단어 문자 (영문, 숫자, _) /\\w+/ → 단어
\\s 공백 문자 /\\s+/ → 하나 이상의 공백
[abc] a, b, c 중 하나 /[abc]/ → "a", "b", "c"
[0-9] 0부터 9까지 /[0-9]+/ → 숫자
{n} 정확히 n개 /a{3}/ → "aaa"
{n,m} n개 이상 m개 이하 /a{2,4}/ → "aa", "aaa", "aaaa"

정규표현식 메서드

const text = "Hello World 123";
const pattern = /\\d+/;

// test(): 패턴이 있는지 확인 (true/false)
console.log(pattern.test(text));  // true

// exec(): 매칭 결과 반환
console.log(pattern.exec(text));  // ["123", index: 12, input: "Hello World 123"]

// match(): 문자열에서 매칭 결과 찾기
console.log(text.match(/\\d+/));  // ["123"]

// search(): 매칭 위치 반환
console.log(text.search(/\\d+/));  // 12

// replace(): 문자열 치환
console.log(text.replace(/\\d+/, "456"));  // "Hello World 456"

// split(): 패턴으로 문자열 분리
console.log("a,b,c".split(/,/));  // ["a", "b", "c"]

실전 활용 예제

1. 이메일 검증

function validateEmail(email) {
    const pattern = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$/;
    return pattern.test(email);
}

console.log(validateEmail("user@example.com"));  // true
console.log(validateEmail("invalid.email"));     // false

2. 전화번호 형식 변환

function formatPhoneNumber(phone) {
    // 숫자만 추출
    const numbers = phone.replace(/\\D/g, '');
    
    // 010-1234-5678 형식으로 변환
    if (numbers.length === 11) {
        return numbers.replace(/(\\d{3})(\\d{4})(\\d{4})/, '$1-$2-$3');
    }
    return phone;
}

console.log(formatPhoneNumber("01012345678"));  // "010-1234-5678"
console.log(formatPhoneNumber("010-1234-5678"));  // "010-1234-5678"

3. HTML 태그 제거

function removeHtmlTags(html) {
    return html.replace(/<[^>]*>/g, '');
}

const html = "

Hello World

"; console.log(removeHtmlTags(html)); // "Hello World"

4. URL 파라미터 추출

function getUrlParams(url) {
    const params = {};
    const pattern = /[?&]([^=]+)=([^&]+)/g;
    let match;
    
    while ((match = pattern.exec(url)) !== null) {
        params[match[1]] = decodeURIComponent(match[2]);
    }
    
    return params;
}

const url = "https://example.com?name=John&age=30";
console.log(getUrlParams(url));  // { name: "John", age: "30" }

정규표현식 플래그

`, problem: "이메일 주소를 검증하는 정규표현식을 작성하세요. 이메일 형식은 user@domain.com 형태입니다.", solution: "const emailPattern = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$/;\n\nfunction validateEmail(email) {\n return emailPattern.test(email);\n}\n\nconsole.log(validateEmail('user@example.com'));", test: (code) => { try { return code.includes('/') && (code.includes('test') || code.includes('match')) && code.includes('@'); } catch (e) { return false; } } }, { title: "Fetch API와 HTTP 통신", content: `

Fetch API란?

Fetch API는 네트워크 요청을 보내고 응답을 받는 현대적인 JavaScript API입니다. 기존의 XMLHttpRequest(XHR)보다 사용하기 쉽고 Promise 기반으로 동작합니다. 서버와의 통신, REST API 호출, 데이터 가져오기 등에 사용됩니다.

기본 Fetch 사용법

// GET 요청 (가장 기본적인 형태)
fetch('https://api.example.com/data')
    .then(response => response.json())
    .then(data => console.log(data))
    .catch(error => console.error('에러:', error));

// async/await 사용
async function fetchData() {
    try {
        const response = await fetch('https://api.example.com/data');
        const data = await response.json();
        console.log(data);
    } catch (error) {
        console.error('에러:', error);
    }
}

다양한 HTTP 메서드

1. GET 요청 (데이터 가져오기)

async function getUsers() {
    const response = await fetch('https://api.example.com/users');
    
    // 응답 상태 확인
    if (!response.ok) {
        throw new Error(\`HTTP 에러! 상태: \${response.status}\`);
    }
    
    const users = await response.json();
    return users;
}

2. POST 요청 (데이터 전송)

async function createUser(userData) {
    const response = await fetch('https://api.example.com/users', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
        },
        body: JSON.stringify(userData)
    });
    
    if (!response.ok) {
        throw new Error('사용자 생성 실패');
    }
    
    return await response.json();
}

// 사용
const newUser = await createUser({
    name: 'John',
    email: 'john@example.com'
});

3. PUT 요청 (데이터 수정)

async function updateUser(userId, userData) {
    const response = await fetch(\`https://api.example.com/users/\${userId}\`, {
        method: 'PUT',
        headers: {
            'Content-Type': 'application/json',
        },
        body: JSON.stringify(userData)
    });
    
    return await response.json();
}

4. DELETE 요청 (데이터 삭제)

async function deleteUser(userId) {
    const response = await fetch(\`https://api.example.com/users/\${userId}\`, {
        method: 'DELETE'
    });
    
    if (response.ok) {
        console.log('사용자 삭제 완료');
    }
}

응답 처리 방법

async function handleResponse() {
    const response = await fetch('https://api.example.com/data');
    
    // 응답 타입에 따라 다른 메서드 사용
    const contentType = response.headers.get('content-type');
    
    if (contentType.includes('application/json')) {
        const data = await response.json();
        console.log('JSON 데이터:', data);
    } else if (contentType.includes('text/html')) {
        const html = await response.text();
        console.log('HTML:', html);
    } else if (contentType.includes('image')) {
        const blob = await response.blob();
        const imageUrl = URL.createObjectURL(blob);
        console.log('이미지 URL:', imageUrl);
    }
}

에러 처리

async function fetchWithErrorHandling(url) {
    try {
        const response = await fetch(url);
        
        // HTTP 에러 상태 확인
        if (!response.ok) {
            if (response.status === 404) {
                throw new Error('리소스를 찾을 수 없습니다');
            } else if (response.status === 500) {
                throw new Error('서버 오류가 발생했습니다');
            } else {
                throw new Error(\`HTTP 에러: \${response.status}\`);
            }
        }
        
        const data = await response.json();
        return data;
        
    } catch (error) {
        if (error.name === 'TypeError') {
            console.error('네트워크 오류:', error.message);
        } else {
            console.error('에러:', error.message);
        }
        throw error;
    }
}

실전 활용 예제

1. 여러 API 동시 호출

async function fetchMultipleAPIs() {
    try {
        const [users, posts, comments] = await Promise.all([
            fetch('https://api.example.com/users').then(r => r.json()),
            fetch('https://api.example.com/posts').then(r => r.json()),
            fetch('https://api.example.com/comments').then(r => r.json())
        ]);
        
        return { users, posts, comments };
    } catch (error) {
        console.error('API 호출 실패:', error);
    }
}

2. 인증 토큰 포함 요청

async function fetchWithAuth(url, token) {
    const response = await fetch(url, {
        headers: {
            'Authorization': \`Bearer \${token}\`,
            'Content-Type': 'application/json'
        }
    });
    
    return await response.json();
}

3. 폼 데이터 전송

async function submitForm(formData) {
    const response = await fetch('https://api.example.com/submit', {
        method: 'POST',
        body: formData  // FormData 객체 직접 전송 가능
    });
    
    return await response.json();
}

// 사용
const form = document.querySelector('form');
const formData = new FormData(form);
await submitForm(formData);
`, problem: "Fetch API를 사용하여 'https://api.example.com/users'에서 사용자 데이터를 가져와서 콘솔에 출력하는 함수를 작성하세요.", solution: "async function fetchUsers() {\n try {\n const response = await fetch('https://api.example.com/users');\n const users = await response.json();\n console.log(users);\n } catch (error) {\n console.error('에러:', error);\n }\n}\n\nfetchUsers();", test: (code) => { try { return code.includes('fetch') && code.includes('await') && (code.includes('json') || code.includes('response')); } catch (e) { return false; } } }, { title: "로컬 스토리지와 세션 스토리지", content: `

웹 스토리지란?

웹 스토리지는 브라우저에 데이터를 저장하는 API입니다. 쿠키와 달리 더 많은 데이터를 저장할 수 있고(약 5-10MB), 서버로 자동 전송되지 않습니다. 로컬 스토리지와 세션 스토리지 두 가지가 있습니다.

로컬 스토리지 (LocalStorage)

로컬 스토리지는 브라우저를 닫아도 데이터가 유지됩니다. 명시적으로 삭제하지 않는 한 계속 저장되어 있습니다.

기본 사용법

// 데이터 저장
localStorage.setItem('username', 'John');
localStorage.setItem('age', '30');

// 데이터 가져오기
const username = localStorage.getItem('username');
console.log(username);  // "John"

// 데이터 삭제
localStorage.removeItem('username');

// 모든 데이터 삭제
localStorage.clear();

// 키 이름 확인
const keys = Object.keys(localStorage);
console.log(keys);  // ['age']

객체 저장하기

// 객체는 JSON으로 변환하여 저장
const user = {
    name: 'John',
    age: 30,
    email: 'john@example.com'
};

localStorage.setItem('user', JSON.stringify(user));

// 가져올 때는 다시 파싱
const savedUser = JSON.parse(localStorage.getItem('user'));
console.log(savedUser);  // { name: 'John', age: 30, email: 'john@example.com' }

세션 스토리지 (SessionStorage)

세션 스토리지는 브라우저 탭을 닫으면 데이터가 삭제됩니다. 같은 사이트의 다른 탭과는 데이터를 공유하지 않습니다.

// 세션 스토리지 사용법 (로컬 스토리지와 동일)
sessionStorage.setItem('sessionId', 'abc123');
const sessionId = sessionStorage.getItem('sessionId');
sessionStorage.removeItem('sessionId');
sessionStorage.clear();

로컬 스토리지 vs 세션 스토리지

특성 로컬 스토리지 세션 스토리지
데이터 유지 브라우저를 닫아도 유지 탭을 닫으면 삭제
공유 범위 같은 도메인의 모든 탭 같은 탭만
용도 사용자 설정, 장바구니 등 임시 데이터, 폼 데이터

실전 활용 예제

1. 사용자 설정 저장

// 테마 설정 저장
function saveTheme(theme) {
    localStorage.setItem('theme', theme);
}

function loadTheme() {
    return localStorage.getItem('theme') || 'light';
}

// 다크 모드 토글
function toggleDarkMode() {
    const currentTheme = loadTheme();
    const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
    saveTheme(newTheme);
    document.body.className = newTheme;
}

// 페이지 로드 시 테마 적용
document.addEventListener('DOMContentLoaded', () => {
    const theme = loadTheme();
    document.body.className = theme;
});

2. 폼 데이터 임시 저장

// 폼 입력 내용 저장 (자동 저장)
const form = document.querySelector('form');

form.addEventListener('input', (e) => {
    const formData = {
        name: form.name.value,
        email: form.email.value,
        message: form.message.value
    };
    sessionStorage.setItem('formDraft', JSON.stringify(formData));
});

// 페이지 로드 시 저장된 데이터 복원
window.addEventListener('load', () => {
    const saved = sessionStorage.getItem('formDraft');
    if (saved) {
        const formData = JSON.parse(saved);
        form.name.value = formData.name || '';
        form.email.value = formData.email || '';
        form.message.value = formData.message || '';
    }
});

// 제출 시 저장된 데이터 삭제
form.addEventListener('submit', () => {
    sessionStorage.removeItem('formDraft');
});

3. 장바구니 구현

// 장바구니 관리
class ShoppingCart {
    constructor() {
        this.items = this.loadCart();
    }
    
    loadCart() {
        const saved = localStorage.getItem('cart');
        return saved ? JSON.parse(saved) : [];
    }
    
    saveCart() {
        localStorage.setItem('cart', JSON.stringify(this.items));
    }
    
    addItem(product) {
        this.items.push(product);
        this.saveCart();
    }
    
    removeItem(productId) {
        this.items = this.items.filter(item => item.id !== productId);
        this.saveCart();
    }
    
    clearCart() {
        this.items = [];
        localStorage.removeItem('cart');
    }
    
    getTotal() {
        return this.items.reduce((sum, item) => sum + item.price, 0);
    }
}

// 사용
const cart = new ShoppingCart();
cart.addItem({ id: 1, name: '상품1', price: 10000 });
console.log(cart.getTotal());  // 10000

주의사항

`, problem: "로컬 스토리지에 사용자 정보 객체를 저장하고, 다시 가져와서 콘솔에 출력하는 코드를 작성하세요. 객체는 { name: 'John', age: 30 } 형태입니다.", solution: "const user = { name: 'John', age: 30 };\nlocalStorage.setItem('user', JSON.stringify(user));\nconst savedUser = JSON.parse(localStorage.getItem('user'));\nconsole.log(savedUser);", test: (code) => { try { return code.includes('localStorage') && code.includes('setItem') && code.includes('getItem') && (code.includes('JSON.stringify') || code.includes('JSON.parse')); } catch (e) { return false; } } }, { title: "웹 컴포넌트", content: `

웹 컴포넌트란?

웹 컴포넌트(Web Components)는 재사용 가능한 커스텀 HTML 요소를 만드는 웹 표준입니다. Custom Elements, Shadow DOM, HTML Templates, ES Modules로 구성됩니다. 프레임워크 없이도 컴포넌트 기반 개발이 가능합니다.

Custom Elements (커스텀 요소)

Custom Elements는 자신만의 HTML 태그를 정의할 수 있게 해줍니다.

// 커스텀 요소 정의
class MyButton extends HTMLElement {
    constructor() {
        super();
        this.textContent = '클릭하세요';
        this.style.padding = '10px 20px';
        this.style.backgroundColor = '#007bff';
        this.style.color = 'white';
        this.style.border = 'none';
        this.style.borderRadius = '5px';
        this.style.cursor = 'pointer';
    }
    
    connectedCallback() {
        this.addEventListener('click', () => {
            alert('버튼이 클릭되었습니다!');
        });
    }
}

// 커스텀 요소 등록
customElements.define('my-button', MyButton);

// HTML에서 사용
// <my-button></my-button>

Shadow DOM

Shadow DOM은 컴포넌트의 스타일과 구조를 외부와 격리시킵니다. 외부 CSS가 컴포넌트 내부에 영향을 주지 않습니다.

class MyCard extends HTMLElement {
    constructor() {
        super();
        
        // Shadow DOM 생성
        const shadow = this.attachShadow({ mode: 'open' });
        
        // 스타일
        const style = document.createElement('style');
        style.textContent = \`
            .card {
                border: 1px solid #ddd;
                border-radius: 8px;
                padding: 20px;
                box-shadow: 0 2px 4px rgba(0,0,0,0.1);
            }
            .title {
                font-size: 20px;
                font-weight: bold;
                margin-bottom: 10px;
            }
        \`;
        
        // 구조
        const card = document.createElement('div');
        card.className = 'card';
        card.innerHTML = \`
            
\${this.getAttribute('title') || '제목'}
</slot>
\`; shadow.appendChild(style); shadow.appendChild(card); } } customElements.define('my-card', MyCard); // HTML에서 사용 // <my-card title="안녕하세요"> // <p>내용입니다</p> // </my-card>

HTML Templates

HTML Templates는 재사용 가능한 HTML 구조를 정의합니다.

// HTML에 템플릿 정의
// <template id="user-card-template">
//     <div class="user-card">
//         <img class="avatar" src="" alt="">
//         <h3 class="name"></h3>
//         <p class="email"></p>
//     </div>
// </template>

class UserCard extends HTMLElement {
    constructor() {
        super();
        const shadow = this.attachShadow({ mode: 'open' });
        
        // 템플릿 가져오기
        const template = document.getElementById('user-card-template');
        const clone = template.content.cloneNode(true);
        
        // 데이터 바인딩
        clone.querySelector('.avatar').src = this.getAttribute('avatar');
        clone.querySelector('.avatar').alt = this.getAttribute('name');
        clone.querySelector('.name').textContent = this.getAttribute('name');
        clone.querySelector('.email').textContent = this.getAttribute('email');
        
        // 스타일 추가
        const style = document.createElement('style');
        style.textContent = \`
            .user-card {
                border: 1px solid #ddd;
                padding: 15px;
                border-radius: 8px;
            }
            .avatar {
                width: 50px;
                height: 50px;
                border-radius: 50%;
            }
        \`;
        
        shadow.appendChild(style);
        shadow.appendChild(clone);
    }
}

customElements.define('user-card', UserCard);

// HTML에서 사용
// <user-card 
//     name="John Doe"
//     email="john@example.com"
//     avatar="https://example.com/avatar.jpg">
// </user-card>

실전 활용 예제

1. 모달 컴포넌트

class MyModal extends HTMLElement {
    constructor() {
        super();
        const shadow = this.attachShadow({ mode: 'open' });
        
        const style = document.createElement('style');
        style.textContent = \`
            .modal-overlay {
                position: fixed;
                top: 0;
                left: 0;
                right: 0;
                bottom: 0;
                background: rgba(0,0,0,0.5);
                display: flex;
                justify-content: center;
                align-items: center;
                z-index: 1000;
            }
            .modal-content {
                background: white;
                padding: 20px;
                border-radius: 8px;
                max-width: 500px;
            }
            .close-btn {
                float: right;
                cursor: pointer;
            }
        \`;
        
        const overlay = document.createElement('div');
        overlay.className = 'modal-overlay';
        overlay.innerHTML = \`
            

웹 컴포넌트의 장점

`, problem: "Custom Elements를 사용하여 'my-button'이라는 커스텀 버튼을 만들고, 클릭 시 '안녕하세요!'를 alert로 표시하세요.", solution: "class MyButton extends HTMLElement {\n constructor() {\n super();\n this.textContent = '클릭하세요';\n }\n \n connectedCallback() {\n this.addEventListener('click', () => {\n alert('안녕하세요!');\n });\n }\n}\n\ncustomElements.define('my-button', MyButton);", test: (code) => { try { return code.includes('HTMLElement') && code.includes('customElements.define') && code.includes('addEventListener'); } catch (e) { return false; } } } ]; // 메인 화면 표시 function showMainScreen() { document.getElementById('mainScreen').style.display = 'block'; document.getElementById('lessonScreens').innerHTML = ''; window.scrollTo(0, 0); // History API: 메인 화면 상태 저장 if (window.history && window.history.pushState) { window.history.pushState({ page: 'main' }, '', window.location.pathname); } } // 강의 화면 표시 function showLesson(chapterNum, useHistory = true) { const lesson = lessons[chapterNum - 1]; if (!lesson) return; document.getElementById('mainScreen').style.display = 'none'; // History API: 현재 장 번호를 상태로 저장 if (useHistory && window.history && window.history.pushState) { window.history.pushState({ page: 'lesson', chapter: chapterNum }, '', `#chapter${chapterNum}`); } const lessonHTML = `

${lesson.title}

${lesson.content}

📝 실습 문제

${lesson.problem}

실행 결과가 여기에 표시됩니다...
✅ 정답입니다! 다음 장으로 넘어갈 수 있습니다.
❌ 아직 정답이 아닙니다. 다시 시도해보세요!
`; document.getElementById('lessonScreens').innerHTML = lessonHTML; window.scrollTo(0, 0); } // 코드 실행 (실제 JavaScript 실행) function runCode(chapterNum) { const code = document.getElementById('codeEditor' + chapterNum).value; const output = document.getElementById('output' + chapterNum); const successMsg = document.getElementById('success' + chapterNum); const errorMsg = document.getElementById('error' + chapterNum); const nextBtn = document.getElementById('nextButton' + chapterNum); const lesson = lessons[chapterNum - 1]; // 출력 초기화 output.textContent = ''; successMsg.classList.remove('show'); errorMsg.classList.remove('show'); nextBtn.classList.remove('show'); // 실제 JavaScript 코드 실행 try { // console.log를 가로채서 출력에 표시 const originalLog = console.log; let logOutput = ''; console.log = function(...args) { logOutput += args.map(arg => typeof arg === 'object' ? JSON.stringify(arg, null, 2) : String(arg) ).join(' ') + '\n'; originalLog.apply(console, args); }; // 코드 실행 eval(code); // console.log 복원 console.log = originalLog; // 출력 표시 if (logOutput) { output.textContent = logOutput.trim(); } else { output.textContent = '코드가 실행되었습니다. (출력 없음)'; } // 코드 검증 if (lesson.test(code)) { successMsg.classList.add('show'); nextBtn.classList.add('show'); } else { errorMsg.classList.add('show'); } } catch (e) { errorMsg.classList.add('show'); output.textContent = '오류: ' + e.message; console.error(e); } } // 다음 장으로 이동 function nextChapter(currentChapter) { if (currentChapter < lessons.length) { showLesson(currentChapter + 1); } else { alert('모든 강의를 완료했습니다! 🎉'); showMainScreen(); } } // History API: 뒤로 가기/앞으로 가기 버튼 처리 window.addEventListener('popstate', function(event) { if (event.state) { if (event.state.page === 'main') { showMainScreen(); } else if (event.state.page === 'lesson' && event.state.chapter) { showLesson(event.state.chapter, false); // History를 다시 추가하지 않음 } } else { // 초기 상태 또는 직접 URL 접근 시 메인 화면 표시 showMainScreen(); } }); // 초기 로드 시 History 상태 설정 window.addEventListener('DOMContentLoaded', function() { if (window.location.hash) { const chapterMatch = window.location.hash.match(/^#chapter(\d+)$/); if (chapterMatch) { const chapterNum = parseInt(chapterMatch[1]); if (chapterNum > 0 && chapterNum <= lessons.length) { showLesson(chapterNum, false); } } } else { // 초기 상태 설정 if (window.history && window.history.replaceState) { window.history.replaceState({ page: 'main' }, '', window.location.pathname); } } }); // 햄버거 메뉴 토글 function toggleMenu() { const menu = document.getElementById('navbarMenu'); const hamburger = document.getElementById('hamburger'); menu.classList.toggle('active'); hamburger.classList.toggle('active'); } // 메뉴 링크 클릭 시 모바일에서 메뉴 닫기 document.addEventListener('DOMContentLoaded', function() { const menuLinks = document.querySelectorAll('.navbar-menu a'); menuLinks.forEach(link => { link.addEventListener('click', function() { if (window.innerWidth <= 768) { const menu = document.getElementById('navbarMenu'); const hamburger = document.getElementById('hamburger'); menu.classList.remove('active'); hamburger.classList.remove('active'); } }); }); });
💊 치료는 의사에게, LMS는 맑은소프트
🎬 스트리밍의 모든 것! 위캔디오