์ฝ˜ํ…์ธ  ๋Œ€ํ‘œ ์ด๋ฏธ์ง€ - ๐Ÿš€ ๋ธŒ๋ผ์šฐ์ € API์™€ Web API๋กœ ๋„ค์ดํ‹ฐ๋ธŒ ๊ธฐ๋Šฅ ์ •๋ณตํ•˜๊ธฐ: ์›น์ด ์•ฑ์ฒ˜๋Ÿผ ๋™์ž‘ํ•˜๋Š” ๋งˆ๋ฒ•

๐Ÿš€ ๋ธŒ๋ผ์šฐ์ € API์™€ Web API๋กœ ๋„ค์ดํ‹ฐ๋ธŒ ๊ธฐ๋Šฅ ์ •๋ณตํ•˜๊ธฐ: ์›น์ด ์•ฑ์ฒ˜๋Ÿผ ๋™์ž‘ํ•˜๋Š” ๋งˆ๋ฒ•

JavaScript๋กœ ์นด๋ฉ”๋ผ, ์œ„์น˜์ •๋ณด, ์•Œ๋ฆผ๊นŒ์ง€! ์›น ๋ธŒ๋ผ์šฐ์ €์˜ ์ˆจ๊ฒจ์ง„ ํŒŒ์›Œ๋ฅผ 100% ํ™œ์šฉํ•˜๋Š” ์‹ค์ „ ๊ฐ€์ด๋“œ ๐Ÿ“ฑโœจ

๐ŸŽฏ ์›น ๋ธŒ๋ผ์šฐ์ €, ์ƒ๊ฐ๋ณด๋‹ค ํ›จ์”ฌ ๋˜‘๋˜‘ํ•˜๋‹ค!

์•ผ, ์†”์งํžˆ ๋งํ•ด์„œ ์˜ˆ์ „์—๋Š” ์›น ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ๊ทธ๋ƒฅ ๋ฌธ์„œ ๋ณด๋Š” ์šฉ๋„์˜€์ž–์•„? ๊ทธ๋Ÿฐ๋ฐ ์ง€๊ธˆ์€ ์™„์ „ ๋‹ค๋ฅด๋‹ค๊ณ ! ๐Ÿ˜ฎ

์š”์ฆ˜ ๋ธŒ๋ผ์šฐ์ €๋Š” ๋„ค์ดํ‹ฐ๋ธŒ ์•ฑ ๋ชป์ง€์•Š์€ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ด. ์นด๋ฉ”๋ผ ์ ‘๊ทผ, GPS ์œ„์น˜ ์ถ”์ , ํ‘ธ์‹œ ์•Œ๋ฆผ, ์‹ฌ์ง€์–ด ๋ธ”๋ฃจํˆฌ์Šค ๊ธฐ๊ธฐ ์—ฐ๊ฒฐ๊นŒ์ง€ ๊ฐ€๋Šฅํ•˜๋‹ค๋‹ˆ๊นŒ?

์ด ๋ชจ๋“  ๊ฒŒ ๊ฐ€๋Šฅํ•œ ์ด์œ ๋Š” ๋ฐ”๋กœ ๋ธŒ๋ผ์šฐ์ € API์™€ Web API ๋•๋ถ„์ด์•ผ. ์ด ์นœ๊ตฌ๋“ค์€ JavaScript๋ฅผ ํ†ตํ•ด ๋ธŒ๋ผ์šฐ์ €์˜ ๊ฐ•๋ ฅํ•œ ๋„ค์ดํ‹ฐ๋ธŒ ๊ธฐ๋Šฅ๋“ค์„ ์šฐ๋ฆฌ๊ฐ€ ์‰ฝ๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” ์ธํ„ฐํŽ˜์ด์Šค์•ผ. ๐ŸŽจ

๐Ÿ’ก ์ž ๊น! API๊ฐ€ ๋ญ๋ƒ๊ณ ? API(Application Programming Interface)๋Š” ์‰ฝ๊ฒŒ ๋งํ•ด์„œ "๊ธฐ๋Šฅ ์‚ฌ์šฉ ์„ค๋ช…์„œ"์•ผ. ๋ณต์žกํ•œ ๋‚ด๋ถ€ ๊ตฌ์กฐ๋Š” ๋ชฐ๋ผ๋„ ๋˜๊ณ , ์ •ํ•ด์ง„ ๋ฐฉ๋ฒ•๋Œ€๋กœ๋งŒ ํ˜ธ์ถœํ•˜๋ฉด ์›ํ•˜๋Š” ๊ธฐ๋Šฅ์„ ์“ธ ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” ๊ฑฐ์ง€.

์˜ˆ๋ฅผ ๋“ค์–ด, ์นด๋ฉ”๋ผ๋ฅผ ์ผœ๋ ค๋ฉด ์นด๋ฉ”๋ผ ํ•˜๋“œ์›จ์–ด๋ฅผ ์ง์ ‘ ์ œ์–ดํ•  ํ•„์š” ์—†์ด navigator.mediaDevices.getUserMedia() ์ด๋ ‡๊ฒŒ๋งŒ ํ˜ธ์ถœํ•˜๋ฉด ๋! ๊ฐ„๋‹จํ•˜์ง€? ๐Ÿ˜Ž
Browser APIs ๐Ÿ“ท ๐Ÿ“ ๐Ÿ”” ๐Ÿ’พ ๐ŸŽค ๐Ÿ“ถ JavaScript๋กœ ๋ชจ๋“  ๊ธฐ๋Šฅ ์ œ์–ด ๊ฐ€๋Šฅ! ์›น ์•ฑ ๋„ค์ดํ‹ฐ๋ธŒ ์š”์ฒญ ๊ธฐ๋Šฅ

๐Ÿ” ๋ธŒ๋ผ์šฐ์ € API vs Web API, ๋ญ๊ฐ€ ๋‹ค๋ฅธ ๊ฑฐ์•ผ?

์ด ๋‘˜์„ ํ—ท๊ฐˆ๋ คํ•˜๋Š” ์‚ฌ๋žŒ๋“ค์ด ์—„์ฒญ ๋งŽ์•„. ์‚ฌ์‹ค ๋‚˜๋„ ์ฒ˜์Œ์—” ๊ทธ๋žฌ๊ฑฐ๋“ ? ๐Ÿ˜…

๊ฐ„๋‹จํ•˜๊ฒŒ ์ •๋ฆฌํ•˜๋ฉด ์ด๋ž˜:

๐ŸŒ Web API

๋” ๋„“์€ ๊ฐœ๋…!

์›น ํ™˜๊ฒฝ์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋ชจ๋“  API๋ฅผ ํ†ต์นญํ•ด. ๋ธŒ๋ผ์šฐ์ € API๋„ ํฌํ•จ๋˜๊ณ , ์„œ๋ฒ„์™€ ํ†ต์‹ ํ•˜๋Š” REST API, ์™ธ๋ถ€ ์„œ๋น„์Šค API(๊ตฌ๊ธ€ ๋งต, ํŽ˜์ด์Šค๋ถ ๋กœ๊ทธ์ธ ๋“ฑ)๋„ ๋‹ค ํฌํ•จ๋˜๋Š” ํฐ ์šฐ์‚ฐ ๊ฐ™์€ ๊ฐœ๋…์ด์•ผ. โ˜‚๏ธ

๐Ÿ–ฅ๏ธ ๋ธŒ๋ผ์šฐ์ € API

๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ œ๊ณตํ•˜๋Š” ๊ฒƒ!

๋ธŒ๋ผ์šฐ์ € ์ž์ฒด์— ๋‚ด์žฅ๋˜์–ด ์žˆ๋Š” API๋“ค์ด์•ผ. DOM ์กฐ์ž‘, ์œ„์น˜์ •๋ณด, ๋ฏธ๋””์–ด ์žฅ์น˜ ์ ‘๊ทผ ๋“ฑ ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ง์ ‘ ์ œ๊ณตํ•˜๋Š” ๊ธฐ๋Šฅ๋“ค. ๋ณ„๋„ ์„ค์น˜๋‚˜ ์™ธ๋ถ€ ์„œ๋น„์Šค ์—†์ด ๋ฐ”๋กœ ์‚ฌ์šฉ ๊ฐ€๋Šฅ! ๐ŸŽ

๐ŸŽ“ ์‰ฝ๊ฒŒ ๊ธฐ์–ตํ•˜๋Š” ๋ฒ• ๋ธŒ๋ผ์šฐ์ € API โŠ‚ Web API
์ฆ‰, ๋ธŒ๋ผ์šฐ์ € API๋Š” Web API์˜ ๋ถ€๋ถ„์ง‘ํ•ฉ์ด์•ผ. ๋ชจ๋“  ๋ธŒ๋ผ์šฐ์ € API๋Š” Web API์ง€๋งŒ, ๋ชจ๋“  Web API๊ฐ€ ๋ธŒ๋ผ์šฐ์ € API๋Š” ์•„๋‹ˆ๋ผ๋Š” ๊ฑฐ!

๐Ÿ“ฑ ์‹ค์ „์—์„œ ๊ฐ€์žฅ ๋งŽ์ด ์“ฐ๋Š” ๋ธŒ๋ผ์šฐ์ € API ๋ฒ ์ŠคํŠธ 10

์ž, ์ด์ œ ์ง„์งœ ์ค‘์š”ํ•œ ๋ถ€๋ถ„์ด์•ผ. ์‹ค๋ฌด์—์„œ ์ •๋ง ์ž์ฃผ ์“ฐ์ด๋Š” API๋“ค์„ ํ•˜๋‚˜์”ฉ ํŒŒํ—ค์ณ๋ณด์ž! ๐Ÿ’ช
์žฌ๋Šฅ๋„ท ๊ฐ™์€ ํ”Œ๋žซํผ์—์„œ๋„ ์ด๋Ÿฐ API๋“ค์„ ํ™œ์šฉํ•ด์„œ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ์—„์ฒญ ๊ฐœ์„ ํ•  ์ˆ˜ ์žˆ๊ฑฐ๋“ .

1๏ธโƒฃ Geolocation API - ์œ„์น˜์ •๋ณด์˜ ๋งˆ๋ฒ•์‚ฌ

์‚ฌ์šฉ์ž์˜ ํ˜„์žฌ ์œ„์น˜๋ฅผ ์•Œ์•„๋‚ด๋Š” API์•ผ. ๋ฐฐ๋‹ฌ ์•ฑ, ๋‚ ์”จ ์•ฑ, ์ฃผ๋ณ€ ๋ง›์ง‘ ์ฐพ๊ธฐ ๋“ฑ ์œ„์น˜ ๊ธฐ๋ฐ˜ ์„œ๋น„์Šค์— ํ•„์ˆ˜! ๐Ÿ—บ๏ธ

// ํ˜„์žฌ ์œ„์น˜ ๊ฐ€์ ธ์˜ค๊ธฐ - ์ง„์งœ ๊ฐ„๋‹จํ•˜๋‹ค!
navigator.geolocation.getCurrentPosition(
  (position) => {
    const latitude = position.coords.latitude;
    const longitude = position.coords.longitude;
    console.log(`ํ˜„์žฌ ์œ„์น˜: ${latitude}, ${longitude}`);
    console.log(`์ •ํ™•๋„: ${position.coords.accuracy}m`);
  },
  (error) => {
    console.error('์œ„์น˜ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์—†์–ด์š”:', error.message);
  },
  {
    enableHighAccuracy: true,  // ๊ณ ์ •๋ฐ€ ๋ชจ๋“œ
    timeout: 5000,             // 5์ดˆ ํƒ€์ž„์•„์›ƒ
    maximumAge: 0              // ์บ์‹œ ์‚ฌ์šฉ ์•ˆ ํ•จ
  }
);

// ์‹ค์‹œ๊ฐ„ ์œ„์น˜ ์ถ”์  (์›€์ง์ž„ ๊ฐ์ง€)
const watchId = navigator.geolocation.watchPosition(
  (position) => {
    console.log('์œ„์น˜ ์—…๋ฐ์ดํŠธ:', position.coords);
  }
);

// ์ถ”์  ์ค‘์ง€
navigator.geolocation.clearWatch(watchId);
๐Ÿ” ๋ณด์•ˆ ์ฃผ์˜์‚ฌํ•ญ โ€ข ๋ฐ˜๋“œ์‹œ HTTPS ํ™˜๊ฒฝ์—์„œ๋งŒ ์ž‘๋™ํ•ด (localhost๋Š” ์˜ˆ์™ธ)
โ€ข ์‚ฌ์šฉ์ž๊ฐ€ ์œ„์น˜ ๊ถŒํ•œ์„ ํ—ˆ์šฉํ•ด์•ผ ํ•จ
โ€ข ์ฒซ ์š”์ฒญ ์‹œ ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ๊ถŒํ•œ ํŒ์—…์„ ๋„์›€
โ€ข ์ •ํ™•๋„๋Š” GPS, Wi-Fi, IP ๋“ฑ์— ๋”ฐ๋ผ ๋‹ฌ๋ผ์ ธ (10m~์ˆ˜km)
2๏ธโƒฃ MediaDevices API - ์นด๋ฉ”๋ผ์™€ ๋งˆ์ดํฌ ์ •๋ณต

ํ™”์ƒ ์ฑ„ํŒ…, ์‚ฌ์ง„ ์ดฌ์˜, ์Œ์„ฑ ๋…น์Œ ๋“ฑ ๋ฏธ๋””์–ด ๊ธฐ๊ธฐ๋ฅผ ์ œ์–ดํ•˜๋Š” ๊ฐ•๋ ฅํ•œ API! ๐Ÿ“น๐ŸŽค

// ์นด๋ฉ”๋ผ + ๋งˆ์ดํฌ ๋™์‹œ์— ์ผœ๊ธฐ
async function startCamera() {
  try {
    const stream = await navigator.mediaDevices.getUserMedia({
      video: {
        width: { ideal: 1920 },
        height: { ideal: 1080 },
        facingMode: 'user'  // 'user' = ์ „๋ฉด, 'environment' = ํ›„๋ฉด
      },
      audio: {
        echoCancellation: true,  // ์—์ฝ” ์ œ๊ฑฐ
        noiseSuppression: true   // ๋…ธ์ด์ฆˆ ์ œ๊ฑฐ
      }
    });
    
    // ๋น„๋””์˜ค ์š”์†Œ์— ์ŠคํŠธ๋ฆผ ์—ฐ๊ฒฐ
    const videoElement = document.querySelector('#myVideo');
    videoElement.srcObject = stream;
    
    console.log('์นด๋ฉ”๋ผ ์ผœ์ง! ๐ŸŽฅ');
  } catch (error) {
    console.error('๋ฏธ๋””์–ด ์ ‘๊ทผ ์‹คํŒจ:', error);
  }
}

// ์‚ฌ์ง„ ์บก์ฒ˜ํ•˜๊ธฐ
function capturePhoto(videoElement) {
  const canvas = document.createElement('canvas');
  canvas.width = videoElement.videoWidth;
  canvas.height = videoElement.videoHeight;
  
  const ctx = canvas.getContext('2d');
  ctx.drawImage(videoElement, 0, 0);
  
  // ์ด๋ฏธ์ง€ ๋ฐ์ดํ„ฐ URL๋กœ ๋ณ€ํ™˜
  const photoData = canvas.toDataURL('image/png');
  console.log('์‚ฌ์ง„ ์ฐ์—ˆ๋‹ค! ๐Ÿ“ธ');
  return photoData;
}

// ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋ฏธ๋””์–ด ์žฅ์น˜ ๋ชฉ๋ก ๊ฐ€์ ธ์˜ค๊ธฐ
async function getDevices() {
  const devices = await navigator.mediaDevices.enumerateDevices();
  const cameras = devices.filter(d => d.kind === 'videoinput');
  const microphones = devices.filter(d => d.kind === 'audioinput');
  
  console.log(`์นด๋ฉ”๋ผ ${cameras.length}๊ฐœ, ๋งˆ์ดํฌ ${microphones.length}๊ฐœ ๋ฐœ๊ฒฌ!`);
}
โœจ ์‹ค์ „ ํ™œ์šฉ ์˜ˆ์‹œ โ€ข ํ™”์ƒ ํšŒ์˜ ์•ฑ (Zoom, Google Meet ์Šคํƒ€์ผ)
โ€ข ํ”„๋กœํ•„ ์‚ฌ์ง„ ์ดฌ์˜ ๊ธฐ๋Šฅ
โ€ข QR ์ฝ”๋“œ ์Šค์บ๋„ˆ
โ€ข ์Œ์„ฑ ๋ฉ”์‹œ์ง€ ๋…น์Œ
โ€ข ์‹ค์‹œ๊ฐ„ ํ•„ํ„ฐ/์ดํŽ™ํŠธ ์ ์šฉ
3๏ธโƒฃ Notification API - ํ‘ธ์‹œ ์•Œ๋ฆผ์˜ ํž˜

์‚ฌ์šฉ์ž์—๊ฒŒ ์ค‘์š”ํ•œ ์ •๋ณด๋ฅผ ์•Œ๋ ค์ฃผ๋Š” ๋ฐ์Šคํฌํ†ฑ/๋ชจ๋ฐ”์ผ ์•Œ๋ฆผ! ๐Ÿ””
๋ธŒ๋ผ์šฐ์ €๊ฐ€ ๋ฐฑ๊ทธ๋ผ์šด๋“œ์— ์žˆ์–ด๋„ ์•Œ๋ฆผ์ด ๋œฌ๋‹ค๋Š” ๊ฒŒ ํ•ต์‹ฌ์ด์•ผ.

// ์•Œ๋ฆผ ๊ถŒํ•œ ์š”์ฒญ
async function requestNotificationPermission() {
  const permission = await Notification.requestPermission();
  
  if (permission === 'granted') {
    console.log('์•Œ๋ฆผ ๊ถŒํ•œ ํš๋“! โœ…');
    showNotification();
  } else if (permission === 'denied') {
    console.log('์•Œ๋ฆผ ๊ถŒํ•œ ๊ฑฐ๋ถ€๋จ โŒ');
  }
}

// ์•Œ๋ฆผ ํ‘œ์‹œํ•˜๊ธฐ
function showNotification() {
  const notification = new Notification('์ƒˆ ๋ฉ”์‹œ์ง€๊ฐ€ ๋„์ฐฉํ–ˆ์–ด์š”! ๐Ÿ’Œ', {
    body: '์žฌ๋Šฅ๋„ท์—์„œ ๋ˆ„๊ตฐ๊ฐ€ ๋‹น์‹ ์˜ ์žฌ๋Šฅ์— ๊ด€์‹ฌ์„ ๋ณด์˜€์–ด์š”!',
    icon: '/icon.png',
    badge: '/badge.png',
    tag: 'message-notification',  // ๊ฐ™์€ ํƒœ๊ทธ๋Š” ํ•˜๋‚˜๋งŒ ํ‘œ์‹œ
    requireInteraction: false,     // ์ž๋™์œผ๋กœ ์‚ฌ๋ผ์ง
    silent: false,                 // ์†Œ๋ฆฌ ์žฌ์ƒ
    vibrate: [200, 100, 200]      // ์ง„๋™ ํŒจํ„ด (๋ชจ๋ฐ”์ผ)
  });
  
  // ์•Œ๋ฆผ ํด๋ฆญ ์ด๋ฒคํŠธ
  notification.onclick = (event) => {
    event.preventDefault();
    window.open('https://www.jaenung.net/messages', '_blank');
    notification.close();
  };
  
  // 3์ดˆ ํ›„ ์ž๋™ ๋‹ซ๊ธฐ
  setTimeout(() => notification.close(), 3000);
}

// ํ˜„์žฌ ๊ถŒํ•œ ์ƒํƒœ ํ™•์ธ
console.log('ํ˜„์žฌ ์•Œ๋ฆผ ๊ถŒํ•œ:', Notification.permission);
โš ๏ธ ์ฃผ์˜ํ•  ์  โ€ข ์‚ฌ์šฉ์ž๊ฐ€ ์•Œ๋ฆผ์„ ์ฐจ๋‹จํ•˜๋ฉด ๋‹ค์‹œ ์š”์ฒญํ•  ์ˆ˜ ์—†์–ด (๋ธŒ๋ผ์šฐ์ € ์„ค์ •์—์„œ ์ง์ ‘ ๋ณ€๊ฒฝํ•ด์•ผ ํ•จ)
โ€ข ๋„ˆ๋ฌด ์ž์ฃผ ์•Œ๋ฆผ ๋ณด๋‚ด๋ฉด ์‚ฌ์šฉ์ž๊ฐ€ ์งœ์ฆ๋‚˜์„œ ์ฐจ๋‹จํ•จ
โ€ข iOS Safari๋Š” PWA๋กœ ์„ค์น˜๋œ ๊ฒฝ์šฐ์—๋งŒ ์•Œ๋ฆผ ์ง€์›
โ€ข ์•Œ๋ฆผ ๊ถŒํ•œ์€ ๋„๋ฉ”์ธ๋ณ„๋กœ ๊ด€๋ฆฌ๋จ
4๏ธโƒฃ Web Storage API - ๋ธŒ๋ผ์šฐ์ €์— ๋ฐ์ดํ„ฐ ์ €์žฅํ•˜๊ธฐ

์ฟ ํ‚ค๋ณด๋‹ค ํ›จ์”ฌ ๊ฐ•๋ ฅํ•˜๊ณ  ์‚ฌ์šฉํ•˜๊ธฐ ์‰ฌ์šด ์ €์žฅ์†Œ! localStorage์™€ sessionStorage ๋‘ ๊ฐ€์ง€๊ฐ€ ์žˆ์–ด. ๐Ÿ’พ

๊ตฌ๋ถ„ localStorage sessionStorage
์ˆ˜๋ช… ์˜๊ตฌ ์ €์žฅ (์ง์ ‘ ์‚ญ์ œ ์ „๊นŒ์ง€) ํƒญ/์ฐฝ ๋‹ซ์œผ๋ฉด ์‚ญ์ œ
์šฉ๋Ÿ‰ ๋ณดํ†ต 5~10MB ๋ณดํ†ต 5~10MB
๊ณต์œ  ๋ฒ”์œ„ ๊ฐ™์€ ๋„๋ฉ”์ธ์˜ ๋ชจ๋“  ํƒญ/์ฐฝ ๊ฐ™์€ ํƒญ/์ฐฝ ๋‚ด์—์„œ๋งŒ
์šฉ๋„ ์„ค์ •, ํ…Œ๋งˆ, ๋กœ๊ทธ์ธ ์ •๋ณด ์ž„์‹œ ๋ฐ์ดํ„ฐ, ํผ ์ž…๋ ฅ๊ฐ’
// localStorage ์‚ฌ์šฉ๋ฒ• - ์ง„์งœ ์‰ฝ๋‹ค!
// ์ €์žฅ
localStorage.setItem('username', '์ฝ”๋”ฉ๋งˆ์Šคํ„ฐ');
localStorage.setItem('theme', 'dark');

// ์ฝ๊ธฐ
const username = localStorage.getItem('username');
console.log(username);  // '์ฝ”๋”ฉ๋งˆ์Šคํ„ฐ'

// ์‚ญ์ œ
localStorage.removeItem('theme');

// ์ „์ฒด ์‚ญ์ œ
localStorage.clear();

// ๊ฐ์ฒด ์ €์žฅํ•˜๊ธฐ (JSON ๋ณ€ํ™˜ ํ•„์š”)
const userInfo = {
  name: '๊น€๊ฐœ๋ฐœ',
  age: 25,
  skills: ['JavaScript', 'React', 'Node.js']
};

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

// ๊ฐ์ฒด ์ฝ๊ธฐ
const savedUser = JSON.parse(localStorage.getItem('userInfo'));
console.log(savedUser.skills);  // ['JavaScript', 'React', 'Node.js']

// sessionStorage๋„ ์‚ฌ์šฉ๋ฒ• ๋˜‘๊ฐ™์Œ!
sessionStorage.setItem('tempData', '์ž„์‹œ ๋ฐ์ดํ„ฐ');

// ์ €์žฅ ๊ณต๊ฐ„ ํ™•์ธ
console.log(`์ €์žฅ๋œ ํ•ญ๋ชฉ ๊ฐœ์ˆ˜: ${localStorage.length}`);

// ๋ชจ๋“  ํ‚ค ์ˆœํšŒ
for (let i = 0; i < localStorage.length; i++) {
  const key = localStorage.key(i);
  console.log(`${key}: ${localStorage.getItem(key)}`);
}
๐Ÿ’ก ์‹ค์ „ ํŒ โ€ข ๋ฏผ๊ฐํ•œ ์ •๋ณด(๋น„๋ฐ€๋ฒˆํ˜ธ, ์นด๋“œ๋ฒˆํ˜ธ)๋Š” ์ ˆ๋Œ€ ์ €์žฅํ•˜์ง€ ๋งˆ! ๐Ÿšซ
โ€ข ์šฉ๋Ÿ‰ ์ œํ•œ์ด ์žˆ์œผ๋‹ˆ ํฐ ๋ฐ์ดํ„ฐ๋Š” IndexedDB ์‚ฌ์šฉ
โ€ข ์ €์žฅ ์ „์— try-catch๋กœ ๊ฐ์‹ธ์„œ ์šฉ๋Ÿ‰ ์ดˆ๊ณผ ์—๋Ÿฌ ์ฒ˜๋ฆฌ
โ€ข ๋‹คํฌ๋ชจ๋“œ ์„ค์ •, ์–ธ์–ด ์„ ํƒ, ์ตœ๊ทผ ๊ฒ€์ƒ‰์–ด ๋“ฑ์— ์™„๋ฒฝ!
5๏ธโƒฃ Fetch API - ์„œ๋ฒ„์™€ ๋Œ€ํ™”ํ•˜๊ธฐ

XMLHttpRequest์˜ ํ˜„๋Œ€์  ๋Œ€์ฒดํ’ˆ! ์„œ๋ฒ„์™€ ๋ฐ์ดํ„ฐ๋ฅผ ์ฃผ๊ณ ๋ฐ›๋Š” ๊ฐ€์žฅ ์„ธ๋ จ๋œ ๋ฐฉ๋ฒ•์ด์•ผ. ๐ŸŒ

// ๊ธฐ๋ณธ GET ์š”์ฒญ
async function fetchData() {
  try {
    const response = await fetch('https://api.example.com/users');
    
    if (!response.ok) {
      throw new Error(`HTTP ์—๋Ÿฌ! ์ƒํƒœ: ${response.status}`);
    }
    
    const data = await response.json();
    console.log('๋ฐ์ดํ„ฐ ๋ฐ›์•„์˜ด:', data);
    return data;
  } catch (error) {
    console.error('์š”์ฒญ ์‹คํŒจ:', error);
  }
}

// POST ์š”์ฒญ (๋ฐ์ดํ„ฐ ์ „์†ก)
async function createUser(userData) {
  const response = await fetch('https://api.example.com/users', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': 'Bearer your-token-here'
    },
    body: JSON.stringify(userData)
  });
  
  const result = await response.json();
  return result;
}

// ํŒŒ์ผ ์—…๋กœ๋“œ
async function uploadFile(file) {
  const formData = new FormData();
  formData.append('file', file);
  formData.append('description', 'ํ”„๋กœํ•„ ์‚ฌ์ง„');
  
  const response = await fetch('https://api.example.com/upload', {
    method: 'POST',
    body: formData  // Content-Type ์ž๋™ ์„ค์ •๋จ
  });
  
  return await response.json();
}

// ํƒ€์ž„์•„์›ƒ ์„ค์ •
async function fetchWithTimeout(url, timeout = 5000) {
  const controller = new AbortController();
  const timeoutId = setTimeout(() => controller.abort(), timeout);
  
  try {
    const response = await fetch(url, {
      signal: controller.signal
    });
    clearTimeout(timeoutId);
    return await response.json();
  } catch (error) {
    if (error.name === 'AbortError') {
      console.log('์š”์ฒญ ์‹œ๊ฐ„ ์ดˆ๊ณผ!');
    }
    throw error;
  }
}

// ๋ณ‘๋ ฌ ์š”์ฒญ (์—ฌ๋Ÿฌ API ๋™์‹œ ํ˜ธ์ถœ)
async function fetchMultiple() {
  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())
  ]);
  
  console.log('๋ชจ๋“  ๋ฐ์ดํ„ฐ ๋ฐ›์•„์˜ด!', { users, posts, comments });
}
๐ŸŽฏ Fetch API์˜ ์žฅ์ 
  • โœ… Promise ๊ธฐ๋ฐ˜์ด๋ผ async/await ์‚ฌ์šฉ ๊ฐ€๋Šฅ
  • โœ… ์ฝ”๋“œ๊ฐ€ ๊น”๋”ํ•˜๊ณ  ์ฝ๊ธฐ ์‰ฌ์›€
  • โœ… Request/Response ๊ฐ์ฒด๋กœ ์„ธ๋ฐ€ํ•œ ์ œ์–ด
  • โœ… CORS ์ฒ˜๋ฆฌ๊ฐ€ ๊ฐ„ํŽธํ•จ
  • โœ… ์ŠคํŠธ๋ฆฌ๋ฐ ์ง€์›์œผ๋กœ ๋Œ€์šฉ๋Ÿ‰ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ ๊ฐ€๋Šฅ
6๏ธโƒฃ Canvas API - ๊ทธ๋ž˜ํ”ฝ์˜ ์„ธ๊ณ„

2D ๊ทธ๋ž˜ํ”ฝ์„ ๊ทธ๋ฆฌ๊ณ , ์ด๋ฏธ์ง€๋ฅผ ์กฐ์ž‘ํ•˜๊ณ , ๊ฒŒ์ž„์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋Š” ๊ฐ•๋ ฅํ•œ API! ๐ŸŽจ

// Canvas ๊ธฐ๋ณธ ์„ค์ •
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');

// ์‚ฌ๊ฐํ˜• ๊ทธ๋ฆฌ๊ธฐ
ctx.fillStyle = '#a8d5e2';
ctx.fillRect(50, 50, 200, 100);  // x, y, width, height

// ์› ๊ทธ๋ฆฌ๊ธฐ
ctx.beginPath();
ctx.arc(300, 200, 50, 0, Math.PI * 2);  // x, y, radius, startAngle, endAngle
ctx.fillStyle = '#c5a8e2';
ctx.fill();

// ํ…์ŠคํŠธ ๊ทธ๋ฆฌ๊ธฐ
ctx.font = 'bold 30px Arial';
ctx.fillStyle = '#5a9fb8';
ctx.fillText('Hello Canvas!', 100, 300);

// ์ด๋ฏธ์ง€ ๊ทธ๋ฆฌ๊ธฐ
const img = new Image();
img.onload = () => {
  ctx.drawImage(img, 0, 0, 200, 200);
};
img.src = 'image.jpg';

// ๊ทธ๋ผ๋””์–ธํŠธ ํšจ๊ณผ
const gradient = ctx.createLinearGradient(0, 0, 200, 0);
gradient.addColorStop(0, '#a8d5e2');
gradient.addColorStop(1, '#c5a8e2');
ctx.fillStyle = gradient;
ctx.fillRect(50, 400, 200, 100);

// ์ด๋ฏธ์ง€ ํ•„ํ„ฐ ์ ์šฉ (ํ”ฝ์…€ ์กฐ์ž‘)
function applyGrayscale() {
  const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
  const data = imageData.data;
  
  for (let i = 0; i < data.length; i += 4) {
    const avg = (data[i] + data[i + 1] + data[i + 2]) / 3;
    data[i] = avg;      // Red
    data[i + 1] = avg;  // Green
    data[i + 2] = avg;  // Blue
  }
  
  ctx.putImageData(imageData, 0, 0);
}

// Canvas๋ฅผ ์ด๋ฏธ์ง€๋กœ ์ €์žฅ
function saveCanvas() {
  const dataURL = canvas.toDataURL('image/png');
  const link = document.createElement('a');
  link.download = 'my-drawing.png';
  link.href = dataURL;
  link.click();
}
๐ŸŽฎ Canvas ํ™œ์šฉ ์‚ฌ๋ก€ โ€ข ๊ฐ„๋‹จํ•œ 2D ๊ฒŒ์ž„ (ํŒฉ๋งจ, ํ…ŒํŠธ๋ฆฌ์Šค ๋“ฑ)
โ€ข ์ฐจํŠธ ๋ฐ ๊ทธ๋ž˜ํ”„ ์‹œ๊ฐํ™”
โ€ข ์ด๋ฏธ์ง€ ํŽธ์ง‘๊ธฐ (ํ•„ํ„ฐ, ํฌ๋กญ, ํšŒ์ „)
โ€ข ์„œ๋ช… ํŒจ๋“œ
โ€ข ์‹ค์‹œ๊ฐ„ ๊ทธ๋ฆผํŒ
โ€ข ๋ฐ์ดํ„ฐ ์‹œ๊ฐํ™” ๋Œ€์‹œ๋ณด๋“œ
7๏ธโƒฃ Web Audio API - ์†Œ๋ฆฌ์˜ ๋งˆ๋ฒ•์‚ฌ

์˜ค๋””์˜ค๋ฅผ ์žฌ์ƒํ•˜๊ณ , ์กฐ์ž‘ํ•˜๊ณ , ๋ถ„์„ํ•˜๋Š” ๊ฐ•๋ ฅํ•œ API! ์Œ์•… ์•ฑ, ๊ฒŒ์ž„ ์‚ฌ์šด๋“œ, ์˜ค๋””์˜ค ์ดํŽ™ํŠธ ๋“ฑ์— ํ•„์ˆ˜์•ผ. ๐ŸŽต

// Audio Context ์ƒ์„ฑ
const audioContext = new (window.AudioContext || window.webkitAudioContext)();

// ์˜ค๋””์˜ค ํŒŒ์ผ ๋กœ๋“œ ๋ฐ ์žฌ์ƒ
async function playSound(url) {
  const response = await fetch(url);
  const arrayBuffer = await response.arrayBuffer();
  const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
  
  const source = audioContext.createBufferSource();
  source.buffer = audioBuffer;
  source.connect(audioContext.destination);
  source.start(0);  // ์ฆ‰์‹œ ์žฌ์ƒ
}

// ๋ณผ๋ฅจ ์กฐ์ ˆ
function playWithVolume(audioBuffer, volume) {
  const source = audioContext.createBufferSource();
  const gainNode = audioContext.createGain();
  
  source.buffer = audioBuffer;
  gainNode.gain.value = volume;  // 0.0 ~ 1.0
  
  source.connect(gainNode);
  gainNode.connect(audioContext.destination);
  source.start(0);
}

// ์ฃผํŒŒ์ˆ˜ ๋ถ„์„ (์Œ์•… ์‹œ๊ฐํ™”)
function createVisualizer() {
  const analyser = audioContext.createAnalyser();
  analyser.fftSize = 2048;
  
  const bufferLength = analyser.frequencyBinCount;
  const dataArray = new Uint8Array(bufferLength);
  
  function draw() {
    analyser.getByteTimeDomainData(dataArray);
    
    // Canvas์— ํŒŒํ˜• ๊ทธ๋ฆฌ๊ธฐ
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.lineWidth = 2;
    ctx.strokeStyle = '#a8d5e2';
    ctx.beginPath();
    
    const sliceWidth = canvas.width / bufferLength;
    let x = 0;
    
    for (let i = 0; i < bufferLength; i++) {
      const v = dataArray[i] / 128.0;
      const y = v * canvas.height / 2;
      
      if (i === 0) {
        ctx.moveTo(x, y);
      } else {
        ctx.lineTo(x, y);
      }
      
      x += sliceWidth;
    }
    
    ctx.stroke();
    requestAnimationFrame(draw);
  }
  
  draw();
}

// ๋งˆ์ดํฌ ์ž…๋ ฅ ๋ฐ›๊ธฐ
async function getMicrophoneInput() {
  const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
  const source = audioContext.createMediaStreamSource(stream);
  
  const analyser = audioContext.createAnalyser();
  source.connect(analyser);
  
  // ์‹ค์‹œ๊ฐ„ ์Œ๋Ÿ‰ ์ธก์ •
  const dataArray = new Uint8Array(analyser.frequencyBinCount);
  
  function checkVolume() {
    analyser.getByteFrequencyData(dataArray);
    const average = dataArray.reduce((a, b) => a + b) / dataArray.length;
    console.log('ํ˜„์žฌ ์Œ๋Ÿ‰:', average);
    requestAnimationFrame(checkVolume);
  }
  
  checkVolume();
}
8๏ธโƒฃ Clipboard API - ๋ณต์‚ฌ/๋ถ™์—ฌ๋„ฃ๊ธฐ ๋งˆ์Šคํ„ฐ

ํด๋ฆฝ๋ณด๋“œ์— ์ ‘๊ทผํ•ด์„œ ํ…์ŠคํŠธ๋‚˜ ์ด๋ฏธ์ง€๋ฅผ ๋ณต์‚ฌ/๋ถ™์—ฌ๋„ฃ๊ธฐ ํ•  ์ˆ˜ ์žˆ์–ด! ๐Ÿ“‹

// ํ…์ŠคํŠธ ๋ณต์‚ฌํ•˜๊ธฐ
async function copyToClipboard(text) {
  try {
    await navigator.clipboard.writeText(text);
    console.log('ํด๋ฆฝ๋ณด๋“œ์— ๋ณต์‚ฌ๋จ! โœ…');
  } catch (error) {
    console.error('๋ณต์‚ฌ ์‹คํŒจ:', error);
  }
}

// ํด๋ฆฝ๋ณด๋“œ์—์„œ ํ…์ŠคํŠธ ์ฝ๊ธฐ
async function pasteFromClipboard() {
  try {
    const text = await navigator.clipboard.readText();
    console.log('๋ถ™์—ฌ๋„ฃ๊ธฐ:', text);
    return text;
  } catch (error) {
    console.error('์ฝ๊ธฐ ์‹คํŒจ:', error);
  }
}

// ์ด๋ฏธ์ง€ ๋ณต์‚ฌํ•˜๊ธฐ
async function copyImageToClipboard(imageBlob) {
  try {
    await navigator.clipboard.write([
      new ClipboardItem({
        'image/png': imageBlob
      })
    ]);
    console.log('์ด๋ฏธ์ง€ ๋ณต์‚ฌ ์™„๋ฃŒ! ๐Ÿ–ผ๏ธ');
  } catch (error) {
    console.error('์ด๋ฏธ์ง€ ๋ณต์‚ฌ ์‹คํŒจ:', error);
  }
}

// ํด๋ฆฝ๋ณด๋“œ ๋ณ€๊ฒฝ ๊ฐ์ง€ (์‹คํ—˜์  ๊ธฐ๋Šฅ)
navigator.clipboard.addEventListener('clipboardchange', () => {
  console.log('ํด๋ฆฝ๋ณด๋“œ๊ฐ€ ๋ณ€๊ฒฝ๋˜์—ˆ์–ด์š”!');
});

// ๊ตฌํ˜• ๋ธŒ๋ผ์šฐ์ € ํ˜ธํ™˜ ๋ฐฉ๋ฒ• (fallback)
function copyTextFallback(text) {
  const textarea = document.createElement('textarea');
  textarea.value = text;
  textarea.style.position = 'fixed';
  textarea.style.opacity = '0';
  document.body.appendChild(textarea);
  textarea.select();
  
  try {
    document.execCommand('copy');
    console.log('๋ณต์‚ฌ ์„ฑ๊ณต (fallback)');
  } catch (error) {
    console.error('๋ณต์‚ฌ ์‹คํŒจ:', error);
  }
  
  document.body.removeChild(textarea);
}
9๏ธโƒฃ Intersection Observer API - ์Šคํฌ๋กค ์ด๋ฒคํŠธ์˜ ํ˜๋ช…

์š”์†Œ๊ฐ€ ํ™”๋ฉด์— ๋ณด์ด๋Š”์ง€ ๊ฐ์ง€ํ•˜๋Š” API! ๋ฌดํ•œ ์Šคํฌ๋กค, ๋ ˆ์ด์ง€ ๋กœ๋”ฉ, ์• ๋‹ˆ๋ฉ”์ด์…˜ ํŠธ๋ฆฌ๊ฑฐ ๋“ฑ์— ์™„๋ฒฝํ•ด. ๐Ÿ‘€

// ๊ธฐ๋ณธ ์‚ฌ์šฉ๋ฒ•
const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      console.log('์š”์†Œ๊ฐ€ ํ™”๋ฉด์— ๋‚˜ํƒ€๋‚จ!', entry.target);
      entry.target.classList.add('visible');
    } else {
      console.log('์š”์†Œ๊ฐ€ ํ™”๋ฉด์—์„œ ์‚ฌ๋ผ์ง!', entry.target);
    }
  });
}, {
  root: null,           // viewport ๊ธฐ์ค€
  rootMargin: '0px',    // ์—ฌ๋ฐฑ
  threshold: 0.5        // 50% ๋ณด์ผ ๋•Œ ํŠธ๋ฆฌ๊ฑฐ
});

// ๊ด€์ฐฐ ์‹œ์ž‘
const targetElement = document.querySelector('.my-element');
observer.observe(targetElement);

// ์ด๋ฏธ์ง€ ๋ ˆ์ด์ง€ ๋กœ๋”ฉ
function lazyLoadImages() {
  const imageObserver = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
      if (entry.isIntersecting) {
        const img = entry.target;
        img.src = img.dataset.src;  // data-src๋ฅผ ์‹ค์ œ src๋กœ
        img.classList.add('loaded');
        imageObserver.unobserve(img);  // ๋กœ๋”ฉ ํ›„ ๊ด€์ฐฐ ์ค‘์ง€
      }
    });
  });
  
  const lazyImages = document.querySelectorAll('img[data-src]');
  lazyImages.forEach(img => imageObserver.observe(img));
}

// ๋ฌดํ•œ ์Šคํฌ๋กค ๊ตฌํ˜„
function infiniteScroll() {
  const sentinel = document.querySelector('.scroll-sentinel');
  
  const scrollObserver = new IntersectionObserver((entries) => {
    if (entries[0].isIntersecting) {
      loadMoreContent();
    }
  }, {
    rootMargin: '200px'  // 200px ์ „์— ๋ฏธ๋ฆฌ ๋กœ๋”ฉ
  });
  
  scrollObserver.observe(sentinel);
}

// ์Šคํฌ๋กค ์• ๋‹ˆ๋ฉ”์ด์…˜
function animateOnScroll() {
  const animateObserver = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
      if (entry.isIntersecting) {
        entry.target.style.opacity = '1';
        entry.target.style.transform = 'translateY(0)';
      }
    });
  }, {
    threshold: 0.3
  });
  
  document.querySelectorAll('.animate-on-scroll').forEach(el => {
    el.style.opacity = '0';
    el.style.transform = 'translateY(50px)';
    el.style.transition = 'all 0.6s ease';
    animateObserver.observe(el);
  });
}
๐Ÿ’ก ์™œ Intersection Observer๊ฐ€ ์ข‹์„๊นŒ? โ€ข ๊ธฐ์กด scroll ์ด๋ฒคํŠธ๋ณด๋‹ค ์„ฑ๋Šฅ์ด ํ›จ์”ฌ ์ข‹์•„ (๋ฉ”์ธ ์Šค๋ ˆ๋“œ ์ฐจ๋‹จ ์—†์Œ)
โ€ข ์ •ํ™•ํ•œ ๊ฐ€์‹œ์„ฑ ํŒ๋‹จ ๊ฐ€๋Šฅ
โ€ข ์ฝ”๋“œ๊ฐ€ ๊ฐ„๊ฒฐํ•˜๊ณ  ์ดํ•ดํ•˜๊ธฐ ์‰ฌ์›€
โ€ข ๋ฐฐํ„ฐ๋ฆฌ ์†Œ๋ชจ๊ฐ€ ์ ์Œ (๋ชจ๋ฐ”์ผ์— ์œ ๋ฆฌ)
๐Ÿ”Ÿ Battery Status API - ๋ฐฐํ„ฐ๋ฆฌ ์ •๋ณด ํ™•์ธ

๊ธฐ๊ธฐ์˜ ๋ฐฐํ„ฐ๋ฆฌ ์ƒํƒœ๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์–ด! ๋ฐฐํ„ฐ๋ฆฌ๊ฐ€ ๋ถ€์กฑํ•  ๋•Œ ๊ธฐ๋Šฅ์„ ์ œํ•œํ•˜๋Š” ๋“ฑ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ๊ฐœ์„ ํ•  ์ˆ˜ ์žˆ์ง€. ๐Ÿ”‹

// ๋ฐฐํ„ฐ๋ฆฌ ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ
async function checkBattery() {
  if ('getBattery' in navigator) {
    const battery = await navigator.getBattery();
    
    console.log(`๋ฐฐํ„ฐ๋ฆฌ ์ž”๋Ÿ‰: ${battery.level * 100}%`);
    console.log(`์ถฉ์ „ ์ค‘: ${battery.charging ? '์˜ˆ' : '์•„๋‹ˆ์˜ค'}`);
    console.log(`์™„์ถฉ๊นŒ์ง€: ${battery.chargingTime}์ดˆ`);
    console.log(`๋ฐฉ์ „๊นŒ์ง€: ${battery.dischargingTime}์ดˆ`);
    
    // ๋ฐฐํ„ฐ๋ฆฌ ๋ณ€ํ™” ๊ฐ์ง€
    battery.addEventListener('levelchange', () => {
      console.log('๋ฐฐํ„ฐ๋ฆฌ ์ž”๋Ÿ‰ ๋ณ€๊ฒฝ:', battery.level * 100 + '%');
    });
    
    battery.addEventListener('chargingchange', () => {
      console.log('์ถฉ์ „ ์ƒํƒœ ๋ณ€๊ฒฝ:', battery.charging);
    });
    
    // ๋ฐฐํ„ฐ๋ฆฌ ๋ถ€์กฑ ์‹œ ๊ฒฝ๊ณ 
    if (battery.level < 0.2 && !battery.charging) {
      alert('๋ฐฐํ„ฐ๋ฆฌ๊ฐ€ ๋ถ€์กฑํ•ฉ๋‹ˆ๋‹ค! โš ๏ธ');
      // ๋ฌด๊ฑฐ์šด ์ž‘์—… ์ค‘๋‹จ, ํ™”๋ฉด ๋ฐ๊ธฐ ์กฐ์ ˆ ๋“ฑ
    }
  } else {
    console.log('Battery API ๋ฏธ์ง€์›');
  }
}

// ๋ฐฐํ„ฐ๋ฆฌ ์ƒํƒœ์— ๋”ฐ๋ฅธ ์ตœ์ ํ™”
async function optimizeByBattery() {
  const battery = await navigator.getBattery();
  
  if (battery.level < 0.3 && !battery.charging) {
    // ์ ˆ์ „ ๋ชจ๋“œ ํ™œ์„ฑํ™”
    disableAnimations();
    reduceUpdateFrequency();
    console.log('์ ˆ์ „ ๋ชจ๋“œ ํ™œ์„ฑํ™”๋จ ๐Ÿ”‹');
  }
}
๋ธŒ๋ผ์šฐ์ € API ํ™œ์šฉ ํ”Œ๋กœ์šฐ ์‚ฌ์šฉ์ž ์•ก์…˜ ๊ถŒํ•œ ์š”์ฒญ (ํ•„์š”์‹œ) API ํ˜ธ์ถœ JavaScript ๋„ค์ดํ‹ฐ๋ธŒ ๊ธฐ๋Šฅ ์‹คํ–‰ ๐Ÿ‘† ๐Ÿ” โšก ๐Ÿ“ฑ ๋ฒ„ํŠผ ํด๋ฆญ ์‚ฌ์šฉ์ž ์Šน์ธ ๋ธŒ๋ผ์šฐ์ € ์ฒ˜๋ฆฌ ์นด๋ฉ”๋ผ/GPS ๋“ฑ ๊ฒฐ๊ณผ ๋ฐ˜ํ™˜ ๋ฐ ํ™”๋ฉด ์—…๋ฐ์ดํŠธ Promise, Callback, Event ๋“ฑ์œผ๋กœ ์ฒ˜๋ฆฌ

๐Ÿ›ก๏ธ ๋ณด์•ˆ๊ณผ ๊ถŒํ•œ ๊ด€๋ฆฌ - ์•ˆ์ „ํ•˜๊ฒŒ ์‚ฌ์šฉํ•˜๊ธฐ

๊ฐ•๋ ฅํ•œ API๋“ค์€ ๋ณด์•ˆ ์œ„ํ—˜๋„ ํ•จ๊ป˜ ๊ฐ€์ ธ์™€. ๊ทธ๋ž˜์„œ ๋ธŒ๋ผ์šฐ์ €๋Š” ์—ฌ๋Ÿฌ ๋ณด์•ˆ ์žฅ์น˜๋ฅผ ๋งˆ๋ จํ•ด๋’€์–ด! ๐Ÿ”’

๐Ÿ” ๊ถŒํ•œ ์‹œ์Šคํ…œ์˜ 3๋‹จ๊ณ„ 1๋‹จ๊ณ„: ์š”์ฒญ JavaScript์—์„œ API ํ˜ธ์ถœ
2๋‹จ๊ณ„: ์Šน์ธ ์‚ฌ์šฉ์ž๊ฐ€ ๋ธŒ๋ผ์šฐ์ € ํŒ์—…์—์„œ ํ—ˆ์šฉ/๊ฑฐ๋ถ€ ์„ ํƒ
3๋‹จ๊ณ„: ์‹คํ–‰ ํ—ˆ์šฉ๋œ ๊ฒฝ์šฐ์—๋งŒ ๊ธฐ๋Šฅ ์ž‘๋™
// Permissions API๋กœ ๊ถŒํ•œ ์ƒํƒœ ํ™•์ธ
async function checkPermission(permissionName) {
  try {
    const result = await navigator.permissions.query({ name: permissionName });
    
    console.log(`${permissionName} ๊ถŒํ•œ:`, result.state);
    // 'granted' (ํ—ˆ์šฉ), 'denied' (๊ฑฐ๋ถ€), 'prompt' (๋ฌผ์–ด๋ณผ ์˜ˆ์ •)
    
    // ๊ถŒํ•œ ๋ณ€๊ฒฝ ๊ฐ์ง€
    result.addEventListener('change', () => {
      console.log('๊ถŒํ•œ ์ƒํƒœ ๋ณ€๊ฒฝ:', result.state);
    });
    
    return result.state;
  } catch (error) {
    console.error('๊ถŒํ•œ ํ™•์ธ ์‹คํŒจ:', error);
  }
}

// ์‚ฌ์šฉ ์˜ˆ์‹œ
await checkPermission('geolocation');
await checkPermission('notifications');
await checkPermission('camera');

// ๊ถŒํ•œ ์š”์ฒญ ์ „ ์‚ฌ์šฉ์ž์—๊ฒŒ ์„ค๋ช…ํ•˜๊ธฐ (UX ๊ฐœ์„ )
async function requestLocationWithExplanation() {
  const permissionStatus = await checkPermission('geolocation');
  
  if (permissionStatus === 'prompt') {
    // ๊ถŒํ•œ ์š”์ฒญ ์ „์— ์™œ ํ•„์š”ํ•œ์ง€ ์„ค๋ช…
    const userConfirmed = confirm(
      '์ฃผ๋ณ€ ์žฌ๋Šฅ ๊ณต์œ ์ž๋ฅผ ์ฐพ๊ธฐ ์œ„ํ•ด ์œ„์น˜ ์ •๋ณด๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ํ—ˆ์šฉํ•˜์‹œ๊ฒ ์–ด์š”?'
    );
    
    if (userConfirmed) {
      navigator.geolocation.getCurrentPosition(
        (position) => console.log('์œ„์น˜ ํš๋“ ์„ฑ๊ณต!'),
        (error) => console.error('์œ„์น˜ ํš๋“ ์‹คํŒจ:', error)
      );
    }
  } else if (permissionStatus === 'denied') {
    alert('์œ„์น˜ ๊ถŒํ•œ์ด ๊ฑฐ๋ถ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๋ธŒ๋ผ์šฐ์ € ์„ค์ •์—์„œ ๋ณ€๊ฒฝํ•ด์ฃผ์„ธ์š”.');
  }
}
โš ๏ธ ๋ณด์•ˆ ์ฒดํฌ๋ฆฌ์ŠคํŠธ
  • โœ… HTTPS ํ•„์ˆ˜ (๋Œ€๋ถ€๋ถ„์˜ API๋Š” ๋ณด์•ˆ ์—ฐ๊ฒฐ์—์„œ๋งŒ ์ž‘๋™)
  • โœ… ์‚ฌ์šฉ์ž ๋™์˜ ์—†์ด ๊ถŒํ•œ ์š”์ฒญ ๊ธˆ์ง€
  • โœ… ๋ฏผ๊ฐํ•œ ๋ฐ์ดํ„ฐ๋Š” ์•”ํ˜ธํ™”ํ•ด์„œ ์ €์žฅ
  • โœ… API ํ˜ธ์ถœ ์‹คํŒจ ์‹œ ์ ์ ˆํ•œ ์—๋Ÿฌ ์ฒ˜๋ฆฌ
  • โœ… ๊ถŒํ•œ ๊ฑฐ๋ถ€ ์‹œ ๋Œ€์ฒด ๊ธฐ๋Šฅ ์ œ๊ณต
  • โœ… ์‚ฌ์šฉ์ž์—๊ฒŒ ์™œ ๊ถŒํ•œ์ด ํ•„์š”ํ•œ์ง€ ๋ช…ํ™•ํžˆ ์„ค๋ช…

๐ŸŒ CORS์™€ Same-Origin Policy

๋ธŒ๋ผ์šฐ์ €๋Š” ๋ณด์•ˆ์„ ์œ„ํ•ด ๋‹ค๋ฅธ ๋„๋ฉ”์ธ์˜ ๋ฆฌ์†Œ์Šค ์ ‘๊ทผ์„ ์ œํ•œํ•ด. ์ด๊ฒŒ ๋ฐ”๋กœ CORS(Cross-Origin Resource Sharing)์•ผ!

// CORS ์—๋Ÿฌ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

// 1. ์„œ๋ฒ„์—์„œ CORS ํ—ค๋” ์„ค์ • (๋ฐฑ์—”๋“œ ์ž‘์—…)
// Access-Control-Allow-Origin: *
// Access-Control-Allow-Methods: GET, POST, PUT, DELETE
// Access-Control-Allow-Headers: Content-Type

// 2. ํ”„๋ก์‹œ ์„œ๋ฒ„ ์‚ฌ์šฉ
const proxyUrl = 'https://cors-anywhere.herokuapp.com/';
const targetUrl = 'https://api.example.com/data';

fetch(proxyUrl + targetUrl)
  .then(response => response.json())
  .then(data => console.log(data));

// 3. JSONP ๋ฐฉ์‹ (๊ตฌํ˜• ๋ฐฉ๋ฒ•, ๊ถŒ์žฅํ•˜์ง€ ์•Š์Œ)
function loadJSONP(url, callback) {
  const script = document.createElement('script');
  script.src = url + '?callback=' + callback;
  document.body.appendChild(script);
}

// 4. ๊ฐœ๋ฐœ ์ค‘์—๋Š” ๋ธŒ๋ผ์šฐ์ € CORS ๋น„ํ™œ์„ฑํ™” (ํ”„๋กœ๋•์…˜์—์„œ๋Š” ์ ˆ๋Œ€ ๊ธˆ์ง€!)
// Chrome: --disable-web-security ํ”Œ๋ž˜๊ทธ๋กœ ์‹คํ–‰

๐Ÿš€ ์‹ค์ „ ํ”„๋กœ์ ํŠธ ์˜ˆ์ œ - ์ข…ํ•ฉ ํ™œ์šฉ

์ž, ์ด์ œ ๋ฐฐ์šด API๋“ค์„ ์กฐํ•ฉํ•ด์„œ ์‹ค์ œ ํ”„๋กœ์ ํŠธ๋ฅผ ๋งŒ๋“ค์–ด๋ณด์ž! ๐Ÿ’ช
์žฌ๋Šฅ๋„ท ๊ฐ™์€ ํ”Œ๋žซํผ์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์‹ค์šฉ์ ์ธ ์˜ˆ์ œ๋“ค์ด์•ผ.

๐Ÿ“ธ ํ”„๋กœ์ ํŠธ 1: ์‹ค์‹œ๊ฐ„ ์‚ฌ์ง„ ์ดฌ์˜ ๋ฐ ํ•„ํ„ฐ ์•ฑ
class PhotoApp {
  constructor() {
    this.stream = null;
    this.canvas = document.getElementById('canvas');
    this.ctx = this.canvas.getContext('2d');
    this.video = document.getElementById('video');
  }
  
  // ์นด๋ฉ”๋ผ ์‹œ์ž‘
  async startCamera() {
    try {
      this.stream = await navigator.mediaDevices.getUserMedia({
        video: { facingMode: 'user', width: 1280, height: 720 },
        audio: false
      });
      
      this.video.srcObject = this.stream;
      await this.video.play();
      
      console.log('์นด๋ฉ”๋ผ ์‹œ์ž‘๋จ! ๐Ÿ“ท');
    } catch (error) {
      console.error('์นด๋ฉ”๋ผ ์ ‘๊ทผ ์‹คํŒจ:', error);
      alert('์นด๋ฉ”๋ผ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.');
    }
  }
  
  // ์‚ฌ์ง„ ์ดฌ์˜
  capturePhoto() {
    this.canvas.width = this.video.videoWidth;
    this.canvas.height = this.video.videoHeight;
    this.ctx.drawImage(this.video, 0, 0);
    
    // ์ดฌ์˜ ํšจ๊ณผ์Œ (Web Audio API)
    this.playShutterSound();
    
    return this.canvas.toDataURL('image/jpeg', 0.9);
  }
  
  // ํ•„ํ„ฐ ์ ์šฉ
  applyFilter(filterType) {
    const imageData = this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height);
    const data = imageData.data;
    
    switch(filterType) {
      case 'grayscale':
        for (let i = 0; i < data.length; i += 4) {
          const avg = (data[i] + data[i+1] + data[i+2]) / 3;
          data[i] = data[i+1] = data[i+2] = avg;
        }
        break;
        
      case 'sepia':
        for (let i = 0; i < data.length; i += 4) {
          const r = data[i], g = data[i+1], b = data[i+2];
          data[i] = Math.min(255, r * 0.393 + g * 0.769 + b * 0.189);
          data[i+1] = Math.min(255, r * 0.349 + g * 0.686 + b * 0.168);
          data[i+2] = Math.min(255, r * 0.272 + g * 0.534 + b * 0.131);
        }
        break;
        
      case 'brightness':
        const brightness = 50;
        for (let i = 0; i < data.length; i += 4) {
          data[i] += brightness;
          data[i+1] += brightness;
          data[i+2] += brightness;
        }
        break;
    }
    
    this.ctx.putImageData(imageData, 0, 0);
  }
  
  // ์‚ฌ์ง„ ์ €์žฅ
  async savePhoto() {
    const dataURL = this.canvas.toDataURL('image/jpeg');
    
    // Clipboard API๋กœ ํด๋ฆฝ๋ณด๋“œ์— ๋ณต์‚ฌ
    const blob = await (await fetch(dataURL)).blob();
    await navigator.clipboard.write([
      new ClipboardItem({ 'image/png': blob })
    ]);
    
    // ์•Œ๋ฆผ ํ‘œ์‹œ
    new Notification('์‚ฌ์ง„ ์ €์žฅ ์™„๋ฃŒ! ๐Ÿ“ธ', {
      body: 'ํด๋ฆฝ๋ณด๋“œ์— ๋ณต์‚ฌ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.',
      icon: dataURL
    });
    
    // localStorage์— ์ €์žฅ (์ตœ๊ทผ ์‚ฌ์ง„ ๋ชฉ๋ก)
    const recentPhotos = JSON.parse(localStorage.getItem('recentPhotos') || '[]');
    recentPhotos.unshift({ url: dataURL, timestamp: Date.now() });
    localStorage.setItem('recentPhotos', JSON.stringify(recentPhotos.slice(0, 10)));
  }
  
  // ์นด๋ฉ”๋ผ ์ข…๋ฃŒ
  stopCamera() {
    if (this.stream) {
      this.stream.getTracks().forEach(track => track.stop());
      this.video.srcObject = null;
      console.log('์นด๋ฉ”๋ผ ์ข…๋ฃŒ๋จ');
    }
  }
  
  playShutterSound() {
    const audioContext = new AudioContext();
    const oscillator = audioContext.createOscillator();
    const gainNode = audioContext.createGain();
    
    oscillator.connect(gainNode);
    gainNode.connect(audioContext.destination);
    
    oscillator.frequency.value = 800;
    gainNode.gain.setValueAtTime(0.3, audioContext.currentTime);
    gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.1);
    
    oscillator.start(audioContext.currentTime);
    oscillator.stop(audioContext.currentTime + 0.1);
  }
}

// ์‚ฌ์šฉ๋ฒ•
const app = new PhotoApp();
document.getElementById('startBtn').addEventListener('click', () => app.startCamera());
document.getElementById('captureBtn').addEventListener('click', () => app.capturePhoto());
document.getElementById('saveBtn').addEventListener('click', () => app.savePhoto());
๐Ÿ—บ๏ธ ํ”„๋กœ์ ํŠธ 2: ์ฃผ๋ณ€ ์žฌ๋Šฅ ๊ณต์œ ์ž ์ฐพ๊ธฐ (์œ„์น˜ ๊ธฐ๋ฐ˜)
class TalentFinder {
  constructor() {
    this.userLocation = null;
    this.talents = [];
  }
  
  // ํ˜„์žฌ ์œ„์น˜ ๊ฐ€์ ธ์˜ค๊ธฐ
  async getCurrentLocation() {
    return new Promise((resolve, reject) => {
      if (!navigator.geolocation) {
        reject(new Error('Geolocation API ๋ฏธ์ง€์›'));
        return;
      }
      
      navigator.geolocation.getCurrentPosition(
        (position) => {
          this.userLocation = {
            lat: position.coords.latitude,
            lng: position.coords.longitude,
            accuracy: position.coords.accuracy
          };
          resolve(this.userLocation);
        },
        (error) => reject(error),
        {
          enableHighAccuracy: true,
          timeout: 10000,
          maximumAge: 60000  // 1๋ถ„๊ฐ„ ์บ์‹œ ์‚ฌ์šฉ
        }
      );
    });
  }
  
  // ์ฃผ๋ณ€ ์žฌ๋Šฅ ๊ณต์œ ์ž ๊ฒ€์ƒ‰
  async findNearbyTalents(radius = 5000) {  // ๋ฐ˜๊ฒฝ 5km
    try {
      await this.getCurrentLocation();
      
      // ์„œ๋ฒ„์— ์œ„์น˜ ์ •๋ณด ์ „์†กํ•˜๊ณ  ์ฃผ๋ณ€ ์žฌ๋Šฅ์ž ๋ชฉ๋ก ๋ฐ›๊ธฐ
      const response = await fetch('https://www.jaenung.net/api/nearby-talents', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          latitude: this.userLocation.lat,
          longitude: this.userLocation.lng,
          radius: radius
        })
      });
      
      this.talents = await response.json();
      
      // ๊ฑฐ๋ฆฌ ๊ณ„์‚ฐ ๋ฐ ์ •๋ ฌ
      this.talents.forEach(talent => {
        talent.distance = this.calculateDistance(
          this.userLocation.lat,
          this.userLocation.lng,
          talent.latitude,
          talent.longitude
        );
      });
      
      this.talents.sort((a, b) => a.distance - b.distance);
      
      // ์•Œ๋ฆผ ์ „์†ก
      if (this.talents.length > 0) {
        new Notification('์ฃผ๋ณ€ ์žฌ๋Šฅ ๋ฐœ๊ฒฌ! ๐ŸŽฏ', {
          body: `${this.talents.length}๋ช…์˜ ์žฌ๋Šฅ ๊ณต์œ ์ž๋ฅผ ์ฐพ์•˜์–ด์š”!`,
          icon: '/icon.png'
        });
      }
      
      return this.talents;
    } catch (error) {
      console.error('์žฌ๋Šฅ ๊ฒ€์ƒ‰ ์‹คํŒจ:', error);
      throw error;
    }
  }
  
  // ๋‘ ์ง€์  ๊ฐ„ ๊ฑฐ๋ฆฌ ๊ณ„์‚ฐ (Haversine ๊ณต์‹)
  calculateDistance(lat1, lng1, lat2, lng2) {
    const R = 6371;  // ์ง€๊ตฌ ๋ฐ˜์ง€๋ฆ„ (km)
    const dLat = this.toRad(lat2 - lat1);
    const dLng = this.toRad(lng2 - lng1);
    
    const a = Math.sin(dLat/2) * Math.sin(dLat/2) +
              Math.cos(this.toRad(lat1)) * Math.cos(this.toRad(lat2)) *
              Math.sin(dLng/2) * Math.sin(dLng/2);
    
    const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
    const distance = R * c;
    
    return Math.round(distance * 1000);  // ๋ฏธํ„ฐ ๋‹จ์œ„๋กœ ๋ฐ˜ํ™˜
  }
  
  toRad(degrees) {
    return degrees * Math.PI / 180;
  }
  
  // ์‹ค์‹œ๊ฐ„ ์œ„์น˜ ์ถ”์  (์ด๋™ ์ค‘)
  watchLocation(callback) {
    return navigator.geolocation.watchPosition(
      (position) => {
        this.userLocation = {
          lat: position.coords.latitude,
          lng: position.coords.longitude
        };
        callback(this.userLocation);
      },
      (error) => console.error('์œ„์น˜ ์ถ”์  ์‹คํŒจ:', error),
      { enableHighAccuracy: true }
    );
  }
  
  // ๊ฒฐ๊ณผ๋ฅผ ํ™”๋ฉด์— ํ‘œ์‹œ
  displayResults() {
    const container = document.getElementById('results');
    container.innerHTML = '';
    
    this.talents.forEach(talent => {
      const card = document.createElement('div');
      card.className = 'talent-card';
      card.innerHTML = `
        <h3>${talent.name}</h3>
        <p>${talent.skill}</p>
        <p>๐Ÿ“ ${talent.distance}m ๊ฑฐ๋ฆฌ</p>
        <button onclick="contactTalent('${talent.id}')">์—ฐ๋ฝํ•˜๊ธฐ</button>
      `;
      container.appendChild(card);
    });
  }
}

// ์‚ฌ์šฉ๋ฒ•
const finder = new TalentFinder();

document.getElementById('searchBtn').addEventListener('click', async () => {
  try {
    await finder.findNearbyTalents(5000);
    finder.displayResults();
  } catch (error) {
    alert('์œ„์น˜ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.');
  }
});
๐Ÿ”” ํ”„๋กœ์ ํŠธ 3: ์‹ค์‹œ๊ฐ„ ์•Œ๋ฆผ ์‹œ์Šคํ…œ
class NotificationManager {
  constructor() {
    this.permission = Notification.permission;
    this.notifications = [];
  }
  
  // ์•Œ๋ฆผ ๊ถŒํ•œ ์š”์ฒญ
  async requestPermission() {
    if (this.permission === 'granted') {
      return true;
    }
    
    const result = await Notification.requestPermission();
    this.permission = result;
    
    if (result === 'granted') {
      this.showWelcomeNotification();
      return true;
    }
    
    return false;
  }
  
  // ํ™˜์˜ ์•Œ๋ฆผ
  showWelcomeNotification() {
    const notification = new Notification('์•Œ๋ฆผ ์„ค์ • ์™„๋ฃŒ! ๐ŸŽ‰', {
      body: '์ด์ œ ์ค‘์š”ํ•œ ์†Œ์‹์„ ๋†“์น˜์ง€ ๋งˆ์„ธ์š”!',
      icon: '/icon.png',
      badge: '/badge.png'
    });
    
    setTimeout(() => notification.close(), 3000);
  }
  
  // ์ƒˆ ๋ฉ”์‹œ์ง€ ์•Œ๋ฆผ
  notifyNewMessage(sender, message) {
    if (this.permission !== 'granted') return;
    
    const notification = new Notification(`${sender}๋‹˜์˜ ๋ฉ”์‹œ์ง€`, {
      body: message.substring(0, 100),
      icon: sender.profileImage,
      tag: 'message-' + sender.id,  // ๊ฐ™์€ ๋ฐœ์‹ ์ž๋Š” ํ•˜๋‚˜๋งŒ ํ‘œ์‹œ
      requireInteraction: true,      // ์‚ฌ์šฉ์ž๊ฐ€ ๋‹ซ์„ ๋•Œ๊นŒ์ง€ ์œ ์ง€
      actions: [
        { action: 'reply', title: '๋‹ต์žฅํ•˜๊ธฐ' },
        { action: 'view', title: '๋ณด๊ธฐ' }
      ]
    });
    
    notification.onclick = () => {
      window.focus();
      window.location.href = '/messages/' + sender.id;
      notification.close();
    };
    
    this.notifications.push(notification);
  }
  
  // ์˜ˆ์•ฝ ์•Œ๋ฆผ (ํŠน์ • ์‹œ๊ฐ„์—)
  scheduleNotification(title, body, timestamp) {
    const now = Date.now();
    const delay = timestamp - now;
    
    if (delay > 0) {
      setTimeout(() => {
        new Notification(title, {
          body: body,
          icon: '/icon.png',
          vibrate: [200, 100, 200]
        });
      }, delay);
      
      // localStorage์— ์ €์žฅ (ํŽ˜์ด์ง€ ์ƒˆ๋กœ๊ณ ์นจ ๋Œ€๋น„)
      const scheduled = JSON.parse(localStorage.getItem('scheduledNotifications') || '[]');
      scheduled.push({ title, body, timestamp });
      localStorage.setItem('scheduledNotifications', JSON.stringify(scheduled));
    }
  }
  
  // ์ฃผ๊ธฐ์  ์•Œ๋ฆผ (๋งค์ผ, ๋งค์ฃผ ๋“ฑ)
  setRecurringNotification(title, body, intervalMs) {
    setInterval(() => {
      if (this.permission === 'granted') {
        new Notification(title, { body, icon: '/icon.png' });
      }
    }, intervalMs);
  }
  
  // ๋ฐฐํ„ฐ๋ฆฌ ๋ถ€์กฑ ์•Œ๋ฆผ
  async checkBatteryAndNotify() {
    if ('getBattery' in navigator) {
      const battery = await navigator.getBattery();
      
      if (battery.level < 0.15 && !battery.charging) {
        new Notification('๋ฐฐํ„ฐ๋ฆฌ ๋ถ€์กฑ! ๐Ÿ”‹', {
          body: `๋ฐฐํ„ฐ๋ฆฌ๊ฐ€ ${Math.round(battery.level * 100)}% ๋‚จ์•˜์Šต๋‹ˆ๋‹ค.`,
          icon: '/battery-low.png',
          requireInteraction: true
        });
      }
    }
  }
  
  // ๋ชจ๋“  ์•Œ๋ฆผ ๋‹ซ๊ธฐ
  closeAll() {
    this.notifications.forEach(n => n.close());
    this.notifications = [];
  }
}

// ์‚ฌ์šฉ๋ฒ•
const notificationManager = new NotificationManager();

// ์ดˆ๊ธฐํ™”
notificationManager.requestPermission();

// ์ƒˆ ๋ฉ”์‹œ์ง€ ์ˆ˜์‹  ์‹œ
socket.on('newMessage', (data) => {
  notificationManager.notifyNewMessage(data.sender, data.message);
});

// 30๋ถ„ ํ›„ ์•Œ๋ฆผ ์˜ˆ์•ฝ
const futureTime = Date.now() + (30 * 60 * 1000);
notificationManager.scheduleNotification(
  'ํšŒ์˜ ์‹œ๊ฐ„์ž…๋‹ˆ๋‹ค!',
  '10๋ถ„ ํ›„ ์˜จ๋ผ์ธ ๋ฏธํŒ…์ด ์‹œ์ž‘๋ฉ๋‹ˆ๋‹ค.',
  futureTime
);

// ๋งค์ผ ์˜ค์ „ 9์‹œ ์•Œ๋ฆผ (24์‹œ๊ฐ„ ๊ฐ„๊ฒฉ)
notificationManager.setRecurringNotification(
  '์ข‹์€ ์•„์นจ์ž…๋‹ˆ๋‹ค! โ˜€๏ธ',
  '์˜ค๋Š˜๋„ ์žฌ๋Šฅ๋„ท์—์„œ ์ƒˆ๋กœ์šด ๊ธฐํšŒ๋ฅผ ์ฐพ์•„๋ณด์„ธ์š”!',
  24 * 60 * 60 * 1000
);

โšก ์„ฑ๋Šฅ ์ตœ์ ํ™” ํŒ

API๋ฅผ ์ž˜ ์“ฐ๋Š” ๊ฒƒ๋„ ์ค‘์š”ํ•˜์ง€๋งŒ, ์„ฑ๋Šฅ์„ ๊ณ ๋ คํ•ด์„œ ์จ์•ผ ์ง„์งœ ํ”„๋กœ์•ผ! ๐ŸŽฏ

๐Ÿš€ ์†๋„ ๊ฐœ์„ 

1. API ํ˜ธ์ถœ ์ตœ์†Œํ™”
๋ถˆํ•„์š”ํ•œ ํ˜ธ์ถœ์„ ์ค„์ด๊ณ  ์บ์‹ฑ ํ™œ์šฉ

2. Debounce/Throttle
์—ฐ์†๋œ ์ด๋ฒคํŠธ๋ฅผ ์ œ์–ดํ•ด์„œ ํ˜ธ์ถœ ํšŸ์ˆ˜ ๊ฐ์†Œ

3. Lazy Loading
ํ•„์š”ํ•  ๋•Œ๋งŒ ๋ฆฌ์†Œ์Šค ๋กœ๋“œ

๐Ÿ’พ ๋ฉ”๋ชจ๋ฆฌ ๊ด€๋ฆฌ

1. ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ ์ •๋ฆฌ
์‚ฌ์šฉ ํ›„ removeEventListener ํ˜ธ์ถœ

2. ์ŠคํŠธ๋ฆผ ์ข…๋ฃŒ
๋ฏธ๋””์–ด ์ŠคํŠธ๋ฆผ์€ ๋ฐ˜๋“œ์‹œ stop() ํ˜ธ์ถœ

3. ํฐ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ
Web Worker๋กœ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ฒ˜๋ฆฌ

๐Ÿ”‹ ๋ฐฐํ„ฐ๋ฆฌ ์ ˆ์•ฝ

1. ์œ„์น˜ ์ถ”์  ๊ฐ„๊ฒฉ ์กฐ์ ˆ
watchPosition ๋Œ€์‹  ํ•„์š”์‹œ๋งŒ getCurrentPosition

2. ์• ๋‹ˆ๋ฉ”์ด์…˜ ์ตœ์ ํ™”
requestAnimationFrame ์‚ฌ์šฉ

3. ์ ˆ์ „ ๋ชจ๋“œ
๋ฐฐํ„ฐ๋ฆฌ ๋ถ€์กฑ ์‹œ ๊ธฐ๋Šฅ ์ œํ•œ

// Debounce ํ•จ์ˆ˜ (์—ฐ์† ํ˜ธ์ถœ ๋ฐฉ์ง€)
function debounce(func, delay) {
  let timeoutId;
  return function(...args) {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => func.apply(this, args), delay);
  };
}

// ๊ฒ€์ƒ‰ ์ž…๋ ฅ ์‹œ API ํ˜ธ์ถœ ์ตœ์ ํ™”
const searchInput = document.getElementById('search');
const debouncedSearch = debounce(async (query) => {
  const results = await fetch(`/api/search?q=${query}`);
  displayResults(await results.json());
}, 300);  // 300ms ๋Œ€๊ธฐ

searchInput.addEventListener('input', (e) => {
  debouncedSearch(e.target.value);
});

// Throttle ํ•จ์ˆ˜ (์ผ์ • ๊ฐ„๊ฒฉ์œผ๋กœ๋งŒ ์‹คํ–‰)
function throttle(func, limit) {
  let inThrottle;
  return function(...args) {
    if (!inThrottle) {
      func.apply(this, args);
      inThrottle = true;
      setTimeout(() => inThrottle = false, limit);
    }
  };
}

// ์Šคํฌ๋กค ์ด๋ฒคํŠธ ์ตœ์ ํ™”
const handleScroll = throttle(() => {
  console.log('์Šคํฌ๋กค ์œ„์น˜:', window.scrollY);
}, 100);  // 100ms๋งˆ๋‹ค ํ•œ ๋ฒˆ๋งŒ ์‹คํ–‰

window.addEventListener('scroll', handleScroll);

// ์บ์‹ฑ ์ „๋žต
class APICache {
  constructor(ttl = 60000) {  // ๊ธฐ๋ณธ 1๋ถ„
    this.cache = new Map();
    this.ttl = ttl;
  }
  
  async get(key, fetchFunction) {
    const cached = this.cache.get(key);
    
    if (cached && Date.now() - cached.timestamp < this.ttl) {
      console.log('์บ์‹œ์—์„œ ๊ฐ€์ ธ์˜ด:', key);
      return cached.data;
    }
    
    console.log('์ƒˆ๋กœ ์š”์ฒญ:', key);
    const data = await fetchFunction();
    this.cache.set(key, { data, timestamp: Date.now() });
    return data;
  }
  
  clear() {
    this.cache.clear();
  }
}

// ์‚ฌ์šฉ ์˜ˆ์‹œ
const cache = new APICache(300000);  // 5๋ถ„ ์บ์‹œ

async function getUserData(userId) {
  return cache.get(`user-${userId}`, async () => {
    const response = await fetch(`/api/users/${userId}`);
    return response.json();
  });
}

// Web Worker๋กœ ๋ฌด๊ฑฐ์šด ์ž‘์—… ์ฒ˜๋ฆฌ
// worker.js ํŒŒ์ผ
self.addEventListener('message', (e) => {
  const { imageData } = e.data;
  
  // ๋ฌด๊ฑฐ์šด ์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌ ์ž‘์—…
  const processed = processImage(imageData);
  
  self.postMessage({ result: processed });
});

// ๋ฉ”์ธ ์Šค๋ ˆ๋“œ
const worker = new Worker('worker.js');

worker.postMessage({ imageData: myImageData });

worker.addEventListener('message', (e) => {
  console.log('์ฒ˜๋ฆฌ ์™„๋ฃŒ:', e.data.result);
});
โœจ ์„ฑ๋Šฅ ์ธก์ • ๋„๊ตฌ โ€ข Chrome DevTools Performance ํƒญ
โ€ข Lighthouse ์ ์ˆ˜ ํ™•์ธ
โ€ข performance.now()๋กœ ์‹คํ–‰ ์‹œ๊ฐ„ ์ธก์ •
โ€ข Network ํƒญ์—์„œ API ํ˜ธ์ถœ ์‹œ๊ฐ„ ๋ถ„์„
โ€ข Memory ํƒญ์—์„œ ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜ ํ™•์ธ

๐ŸŒ ๋ธŒ๋ผ์šฐ์ € ํ˜ธํ™˜์„ฑ ์ฒดํฌ

๋ชจ๋“  ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ๋ชจ๋“  API๋ฅผ ์ง€์›ํ•˜๋Š” ๊ฑด ์•„๋‹ˆ์•ผ. ํ˜ธํ™˜์„ฑ ์ฒดํฌ๋Š” ํ•„์ˆ˜! ๐Ÿ”

API Chrome Firefox Safari Edge
Geolocation โœ… 5+ โœ… 3.5+ โœ… 5+ โœ… 9+
MediaDevices โœ… 53+ โœ… 36+ โœ… 11+ โœ… 12+
Notification โœ… 22+ โœ… 22+ โš ๏ธ 16+ (PWA๋งŒ) โœ… 14+
Web Storage โœ… 4+ โœ… 3.5+ โœ… 4+ โœ… 8+
Fetch โœ… 42+ โœ… 39+ โœ… 10.1+ โœ… 14+
Canvas โœ… 4+ โœ… 2+ โœ… 3.1+ โœ… 9+
Web Audio โœ… 35+ โœ… 25+ โœ… 14.1+ โœ… 12+
Clipboard โœ… 66+ โœ… 63+ โœ… 13.1+ โœ… 79+
// ๊ธฐ๋Šฅ ๊ฐ์ง€ (Feature Detection)
function checkAPISupport() {
  const support = {
    geolocation: 'geolocation' in navigator,
    notification: 'Notification' in window,
    mediaDevices: 'mediaDevices' in navigator,
    localStorage: typeof Storage !== 'undefined',
    fetch: 'fetch' in window,
    webAudio: 'AudioContext' in window || 'webkitAudioContext' in window,
    canvas: (() => {
      const canvas = document.createElement('canvas');
      return !!(canvas.getContext && canvas.getContext('2d'));
    })(),
    clipboard: 'clipboard' in navigator,
    battery: 'getBattery' in navigator,
    intersectionObserver: 'IntersectionObserver' in window
  };
  
  console.log('๋ธŒ๋ผ์šฐ์ € API ์ง€์› ํ˜„ํ™ฉ:', support);
  return support;
}

// ํด๋ฆฌํ•„ ๋กœ๋“œ (๊ตฌํ˜• ๋ธŒ๋ผ์šฐ์ € ์ง€์›)
function loadPolyfills() {
  // Fetch API ํด๋ฆฌํ•„
  if (!window.fetch) {
    const script = document.createElement('script');
    script.src = 'https://cdn.jsdelivr.net/npm/whatwg-fetch@3.6.2/dist/fetch.umd.js';
    document.head.appendChild(script);
  }
  
  // Intersection Observer ํด๋ฆฌํ•„
  if (!('IntersectionObserver' in window)) {
    const script = document.createElement('script');
    script.src = 'https://polyfill.io/v3/polyfill.min.js?features=IntersectionObserver';
    document.head.appendChild(script);
  }
}

// ์•ˆ์ „ํ•œ API ์‚ฌ์šฉ
async function safeGetLocation() {
  if (!('geolocation' in navigator)) {
    console.error('Geolocation API ๋ฏธ์ง€์›');
    // ๋Œ€์ฒด ๋ฐฉ๋ฒ•: IP ๊ธฐ๋ฐ˜ ์œ„์น˜ ์ถ”์ •
    return await getLocationByIP();
  }
  
  return new Promise((resolve, reject) => {
    navigator.geolocation.getCurrentPosition(resolve, reject);
  });
}

// ๋Œ€์ฒด ๊ธฐ๋Šฅ ์ œ๊ณต
function showNotificationFallback(title, message) {
  if ('Notification' in window && Notification.permission === 'granted') {
    new Notification(title, { body: message });
  } else {
    // ๋Œ€์ฒด: ํŽ˜์ด์ง€ ๋‚ด ์•Œ๋ฆผ ํ‘œ์‹œ
    const toast = document.createElement('div');
    toast.className = 'toast-notification';
    toast.innerHTML = `<strong>${title}</strong><p>${message}</p>`;
    document.body.appendChild(toast);
    
    setTimeout(() => toast.remove(), 3000);
  }
}
๐Ÿ’ก ํ˜ธํ™˜์„ฑ ์ฒดํฌ ์‚ฌ์ดํŠธ โ€ข Can I Use (caniuse.com) - ๊ฐ€์žฅ ์œ ๋ช…ํ•œ ํ˜ธํ™˜์„ฑ ์ฒดํฌ ์‚ฌ์ดํŠธ
โ€ข MDN Web Docs - ๊ฐ API ๋ฌธ์„œ์— ๋ธŒ๋ผ์šฐ์ € ์ง€์› ํ‘œ ํฌํ•จ
โ€ข Modernizr - ์ž๋™์œผ๋กœ ๊ธฐ๋Šฅ ๊ฐ์ง€ํ•ด์ฃผ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

๐ŸŽ“ ํ•™์Šต ๋ฆฌ์†Œ์Šค ๋ฐ ๋‹ค์Œ ๋‹จ๊ณ„

์—ฌ๊ธฐ๊นŒ์ง€ ์™”๋‹ค๋ฉด ์ด๋ฏธ ๋ธŒ๋ผ์šฐ์ € API์˜ 80%๋Š” ๋งˆ์Šคํ„ฐํ•œ ๊ฑฐ์•ผ! ๐Ÿ‘
์ด์ œ ๋” ๊นŠ์ด ํŒŒ๊ณ ๋“ค ์ฐจ๋ก€์ง€.

๐Ÿ“š ์ถ”์ฒœ ํ•™์Šต ์ž๋ฃŒ ๊ณต์‹ ๋ฌธ์„œ
โ€ข MDN Web Docs (developer.mozilla.org) - ๊ฐ€์žฅ ์ •ํ™•ํ•˜๊ณ  ์ƒ์„ธํ•œ ๋ฌธ์„œ
โ€ข Web.dev by Google - ์‹ค์ „ ์˜ˆ์ œ์™€ ๋ฒ ์ŠคํŠธ ํ”„๋ž™ํ‹ฐ์Šค
โ€ข W3C Specifications - ํ‘œ์ค€ ์ŠคํŽ™ ๋ฌธ์„œ

์‹ค์Šต ํ”Œ๋žซํผ
โ€ข CodePen - ๋ธŒ๋ผ์šฐ์ € API ์‹คํ—˜ํ•˜๊ธฐ ์ข‹์Œ
โ€ข JSFiddle - ๋น ๋ฅธ ํ”„๋กœํ† ํƒ€์ดํ•‘
โ€ข Glitch - ํ’€์Šคํƒ ํ”„๋กœ์ ํŠธ ํ˜ธ์ŠคํŒ…

์ปค๋ฎค๋‹ˆํ‹ฐ
โ€ข Stack Overflow - ๋ฌธ์ œ ํ•ด๊ฒฐ
โ€ข Reddit r/webdev - ์ตœ์‹  ํŠธ๋ Œ๋“œ
โ€ข Dev.to - ๊ฐœ๋ฐœ์ž ๋ธ”๋กœ๊ทธ ํ”Œ๋žซํผ

๐Ÿš€ ๋‹ค์Œ ๋‹จ๊ณ„๋กœ ๋‚˜์•„๊ฐ€๊ธฐ

์ดˆ๊ธ‰ โ†’ ์ค‘๊ธ‰

โœ… ๋ชจ๋“  ์ฃผ์š” API ํ•œ ๋ฒˆ์”ฉ ์‚ฌ์šฉํ•ด๋ณด๊ธฐ
โœ… ๊ฐ„๋‹จํ•œ ํ”„๋กœ์ ํŠธ 3๊ฐœ ์ด์ƒ ์™„์„ฑ
โœ… ์—๋Ÿฌ ์ฒ˜๋ฆฌ ๋ฐ ์˜ˆ์™ธ ์ƒํ™ฉ ๋Œ€์‘
โœ… ๋ธŒ๋ผ์šฐ์ € ํ˜ธํ™˜์„ฑ ๊ณ ๋ ค

์ค‘๊ธ‰ โ†’ ๊ณ ๊ธ‰

โœ… ์—ฌ๋Ÿฌ API ์กฐํ•ฉํ•ด์„œ ๋ณต์žกํ•œ ๊ธฐ๋Šฅ ๊ตฌํ˜„
โœ… ์„ฑ๋Šฅ ์ตœ์ ํ™” ๋ฐ ๋ฉ”๋ชจ๋ฆฌ ๊ด€๋ฆฌ
โœ… PWA(Progressive Web App) ๊ฐœ๋ฐœ
โœ… WebRTC, WebGL ๋“ฑ ๊ณ ๊ธ‰ API ํ•™์Šต

๊ณ ๊ธ‰ โ†’ ์ „๋ฌธ๊ฐ€

โœ… ์˜คํ”ˆ์†Œ์Šค ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๊ธฐ์—ฌ
โœ… ํฌ๋กœ์Šค ๋ธŒ๋ผ์šฐ์ € ์ด์Šˆ ํ•ด๊ฒฐ ๋Šฅ๋ ฅ
โœ… ๋ณด์•ˆ ์ทจ์•ฝ์  ๋ถ„์„ ๋ฐ ๋Œ€์‘
โœ… ์ƒˆ๋กœ์šด API ์ŠคํŽ™ ๋ฌธ์„œ ์ฝ๊ณ  ์ ์šฉ

๐Ÿ”ฎ ๋ฏธ๋ž˜์˜ Web API๋“ค

์›น ๊ธฐ์ˆ ์€ ๊ณ„์† ๋ฐœ์ „ํ•˜๊ณ  ์žˆ์–ด! ์•ž์œผ๋กœ ์ฃผ๋ชฉํ•ด์•ผ ํ•  API๋“ค์„ ์†Œ๊ฐœํ• ๊ฒŒ. ๐ŸŒŸ

๐Ÿ†• ์‹คํ—˜์ /์‹ ๊ทœ API Web Bluetooth API ๋ธ”๋ฃจํˆฌ์Šค ๊ธฐ๊ธฐ ์—ฐ๊ฒฐ
Web NFC API NFC ํƒœ๊ทธ ์ฝ๊ธฐ/์“ฐ๊ธฐ
Web USB API USB ์žฅ์น˜ ์ œ์–ด
File System Access API ๋กœ์ปฌ ํŒŒ์ผ ์‹œ์Šคํ…œ ์ ‘๊ทผ
WebXR Device API VR/AR ๊ฒฝํ—˜ ์ œ๊ณต
Web Share API ๋„ค์ดํ‹ฐ๋ธŒ ๊ณต์œ  ๊ธฐ๋Šฅ
Payment Request API ๊ฐ„ํŽธ ๊ฒฐ์ œ
Web Authentication API ์ƒ์ฒด ์ธ์ฆ
// Web Share API ์˜ˆ์ œ (๋ชจ๋ฐ”์ผ์—์„œ ํŠนํžˆ ์œ ์šฉ)
async function shareContent() {
  if (navigator.share) {
    try {
      await navigator.share({
        title: '์žฌ๋Šฅ๋„ท์—์„œ ๋ฐœ๊ฒฌํ•œ ๋ฉ‹์ง„ ์žฌ๋Šฅ!',
        text: '์ด ์žฌ๋Šฅ ๊ณต์œ ์ž๋ฅผ ํ™•์ธํ•ด๋ณด์„ธ์š”!',
        url: 'https://www.jaenung.net/talent/12345'
      });
      console.log('๊ณต์œ  ์„ฑ๊ณต! ๐ŸŽ‰');
    } catch (error) {
      console.error('๊ณต์œ  ์‹คํŒจ:', error);
    }
  } else {
    // ๋Œ€์ฒด: ํด๋ฆฝ๋ณด๋“œ์— ๋ณต์‚ฌ
    await navigator.clipboard.writeText(window.location.href);
    alert('๋งํฌ๊ฐ€ ๋ณต์‚ฌ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!');
  }
}

// File System Access API ์˜ˆ์ œ (Chrome 86+)
async function saveFile() {
  try {
    const handle = await window.showSaveFilePicker({
      suggestedName: 'my-file.txt',
      types: [{
        description: 'Text Files',
        accept: { 'text/plain': ['.txt'] }
      }]
    });
    
    const writable = await handle.createWritable();
    await writable.write('Hello, World!');
    await writable.close();
    
    console.log('ํŒŒ์ผ ์ €์žฅ ์™„๋ฃŒ! ๐Ÿ’พ');
  } catch (error) {
    console.error('ํŒŒ์ผ ์ €์žฅ ์‹คํŒจ:', error);
  }
}

// Web Bluetooth API ์˜ˆ์ œ (์‹คํ—˜์ )
async function connectBluetooth() {
  try {
    const device = await navigator.bluetooth.requestDevice({
      filters: [{ services: ['heart_rate'] }]
    });
    
    const server = await device.gatt.connect();
    console.log('๋ธ”๋ฃจํˆฌ์Šค ์—ฐ๊ฒฐ๋จ:', device.name);
    
    // ์‹ฌ๋ฐ•์ˆ˜ ์„œ๋น„์Šค ์‚ฌ์šฉ
    const service = await server.getPrimaryService('heart_rate');
    const characteristic = await service.getCharacteristic('heart_rate_measurement');
    
    characteristic.addEventListener('characteristicvaluechanged', (event) => {
      const value = event.target.value;
      console.log('์‹ฌ๋ฐ•์ˆ˜:', value.getUint8(1));
    });
    
    await characteristic.startNotifications();
  } catch (error) {
    console.error('๋ธ”๋ฃจํˆฌ์Šค ์—ฐ๊ฒฐ ์‹คํŒจ:', error);
  }
}

๐Ÿ’ก ์‹ค๋ฌด ํŒ & ๋ฒ ์ŠคํŠธ ํ”„๋ž™ํ‹ฐ์Šค

๋งˆ์ง€๋ง‰์œผ๋กœ ์‹ค๋ฌด์—์„œ ์ •๋ง ์ค‘์š”ํ•œ ํŒ๋“ค์„ ์ •๋ฆฌํ•ด๋ณผ๊ฒŒ! ์ด๊ฑด ๊ฒฝํ—˜์—์„œ ๋‚˜์˜จ ์ง„์งœ ๋…ธํ•˜์šฐ์•ผ. ๐ŸŽฏ

โš ๏ธ ํ”ํ•œ ์‹ค์ˆ˜๋“ค
  • โŒ ๊ถŒํ•œ ์š”์ฒญ์„ ํŽ˜์ด์ง€ ๋กœ๋“œ ์ฆ‰์‹œ ํ•˜๊ธฐ (์‚ฌ์šฉ์ž ๊ฒฝํ—˜ ๋‚˜์จ)
  • โŒ ์—๋Ÿฌ ์ฒ˜๋ฆฌ๋ฅผ ์•ˆ ํ•˜๊ณ  API ํ˜ธ์ถœ๋งŒ ํ•˜๊ธฐ
  • โŒ ๋ฏธ๋””์–ด ์ŠคํŠธ๋ฆผ์„ ์ข…๋ฃŒํ•˜์ง€ ์•Š๊ณ  ๋ฐฉ์น˜
  • โŒ localStorage์— ๋ฏผ๊ฐํ•œ ์ •๋ณด ์ €์žฅ
  • โŒ ๋ชจ๋“  ๋ธŒ๋ผ์šฐ์ €์—์„œ ๋™์ผํ•˜๊ฒŒ ์ž‘๋™ํ•  ๊ฑฐ๋ผ ๊ฐ€์ •
  • โŒ API ํ˜ธ์ถœ ๊ฒฐ๊ณผ๋ฅผ ์บ์‹ฑํ•˜์ง€ ์•Š๊ณ  ๋งค๋ฒˆ ์ƒˆ๋กœ ์š”์ฒญ
โœ… ์˜ฌ๋ฐ”๋ฅธ ์ ‘๊ทผ๋ฒ• โœจ ์‚ฌ์šฉ์ž ์•ก์…˜ ํ›„์— ๊ถŒํ•œ ์š”์ฒญ (๋ฒ„ํŠผ ํด๋ฆญ ๋“ฑ)
โœจ ํ•ญ์ƒ try-catch๋กœ ์—๋Ÿฌ ์ฒ˜๋ฆฌ
โœจ ์‚ฌ์šฉ ์™„๋ฃŒ ํ›„ ๋ฆฌ์†Œ์Šค ์ •๋ฆฌ (stop, close, remove ๋“ฑ)
โœจ ๋ฏผ๊ฐํ•œ ๋ฐ์ดํ„ฐ๋Š” ์„œ๋ฒ„์— ์ €์žฅํ•˜๊ณ  ํ† ํฐ๋งŒ ํด๋ผ์ด์–ธํŠธ์—
โœจ ๊ธฐ๋Šฅ ๊ฐ์ง€(Feature Detection)๋กœ ํ˜ธํ™˜์„ฑ ์ฒดํฌ
โœจ ์ ์ ˆํ•œ ์บ์‹ฑ ์ „๋žต์œผ๋กœ ์„ฑ๋Šฅ ๊ฐœ์„ 
โœจ ์‚ฌ์šฉ์ž์—๊ฒŒ ๋ช…ํ™•ํ•œ ํ”ผ๋“œ๋ฐฑ ์ œ๊ณต (๋กœ๋”ฉ, ์„ฑ๊ณต, ์‹คํŒจ)
// ์™„๋ฒฝํ•œ API ํ˜ธ์ถœ ํŒจํ„ด
class APIHandler {
  constructor() {
    this.cache = new Map();
    this.loading = new Set();
  }
  
  async callAPI(endpoint, options = {}) {
    const cacheKey = `${endpoint}-${JSON.stringify(options)}`;
    
    // 1. ์บ์‹œ ํ™•์ธ
    if (this.cache.has(cacheKey)) {
      const cached = this.cache.get(cacheKey);
      if (Date.now() - cached.timestamp < 60000) {  // 1๋ถ„ ์บ์‹œ
        return cached.data;
      }
    }
    
    // 2. ์ค‘๋ณต ์š”์ฒญ ๋ฐฉ์ง€
    if (this.loading.has(cacheKey)) {
      return new Promise((resolve) => {
        const checkInterval = setInterval(() => {
          if (!this.loading.has(cacheKey)) {
            clearInterval(checkInterval);
            resolve(this.cache.get(cacheKey)?.data);
          }
        }, 100);
      });
    }
    
    this.loading.add(cacheKey);
    
    try {
      // 3. ๋กœ๋”ฉ UI ํ‘œ์‹œ
      this.showLoading();
      
      // 4. API ํ˜ธ์ถœ (ํƒ€์ž„์•„์›ƒ ํฌํ•จ)
      const controller = new AbortController();
      const timeoutId = setTimeout(() => controller.abort(), 10000);
      
      const response = await fetch(endpoint, {
        ...options,
        signal: controller.signal
      });
      
      clearTimeout(timeoutId);
      
      // 5. ์‘๋‹ต ๊ฒ€์ฆ
      if (!response.ok) {
        throw new Error(`HTTP ${response.status}: ${response.statusText}`);
      }
      
      const data = await response.json();
      
      // 6. ์บ์‹œ ์ €์žฅ
      this.cache.set(cacheKey, {
        data,
        timestamp: Date.now()
      });
      
      // 7. ์„ฑ๊ณต ํ”ผ๋“œ๋ฐฑ
      this.showSuccess('๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์™”์Šต๋‹ˆ๋‹ค!');
      
      return data;
      
    } catch (error) {
      // 8. ์—๋Ÿฌ ์ฒ˜๋ฆฌ
      console.error('API ํ˜ธ์ถœ ์‹คํŒจ:', error);
      
      if (error.name === 'AbortError') {
        this.showError('์š”์ฒญ ์‹œ๊ฐ„์ด ์ดˆ๊ณผ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.');
      } else if (!navigator.onLine) {
        this.showError('์ธํ„ฐ๋„ท ์—ฐ๊ฒฐ์„ ํ™•์ธํ•ด์ฃผ์„ธ์š”.');
      } else {
        this.showError('๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š”๋ฐ ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.');
      }
      
      throw error;
      
    } finally {
      // 9. ์ •๋ฆฌ ์ž‘์—…
      this.loading.delete(cacheKey);
      this.hideLoading();
    }
  }
  
  showLoading() {
    document.getElementById('loader')?.classList.add('active');
  }
  
  hideLoading() {
    document.getElementById('loader')?.classList.remove('active');
  }
  
  showSuccess(message) {
    this.showToast(message, 'success');
  }
  
  showError(message) {
    this.showToast(message, 'error');
  }
  
  showToast(message, type) {
    const toast = document.createElement('div');
    toast.className = `toast toast-${type}`;
    toast.textContent = message;
    document.body.appendChild(toast);
    
    setTimeout(() => toast.remove(), 3000);
  }
}

// ์‚ฌ์šฉ ์˜ˆ์‹œ
const api = new APIHandler();

async function loadUserData() {
  try {
    const userData = await api.callAPI('/api/user/profile');
    displayUserProfile(userData);
  } catch (error) {
    // ์—๋Ÿฌ๋Š” ์ด๋ฏธ APIHandler์—์„œ ์ฒ˜๋ฆฌ๋จ
    console.log('์‚ฌ์šฉ์ž ๋ฐ์ดํ„ฐ ๋กœ๋“œ ์‹คํŒจ');
  }
}

๐ŸŽจ UX ๊ฐœ์„  ํŒ

์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ์œ„ํ•œ ์ฒดํฌ๋ฆฌ์ŠคํŠธ ๐Ÿ“ฑ ๋ชจ๋ฐ”์ผ ์ตœ์ ํ™”
โ€ข ํ„ฐ์น˜ ์ด๋ฒคํŠธ ์ง€์›
โ€ข ํ™”๋ฉด ํฌ๊ธฐ์— ๋”ฐ๋ฅธ ๋ฐ˜์‘ํ˜• ๋””์ž์ธ
โ€ข ๋ชจ๋ฐ”์ผ ๋ฐ์ดํ„ฐ ์‚ฌ์šฉ๋Ÿ‰ ๊ณ ๋ ค

โฑ๏ธ ๋กœ๋”ฉ ์ƒํƒœ ํ‘œ์‹œ
โ€ข ์Šคํ”ผ๋„ˆ, ํ”„๋กœ๊ทธ๋ ˆ์Šค ๋ฐ” ๋“ฑ ์‹œ๊ฐ์  ํ”ผ๋“œ๋ฐฑ
โ€ข ์˜ˆ์ƒ ์†Œ์š” ์‹œ๊ฐ„ ํ‘œ์‹œ
โ€ข ์Šค์ผˆ๋ ˆํ†ค UI๋กœ ๋ ˆ์ด์•„์›ƒ ๋ฏธ๋ฆฌ ๋ณด์—ฌ์ฃผ๊ธฐ

๐Ÿ”” ๋ช…ํ™•ํ•œ ํ”ผ๋“œ๋ฐฑ
โ€ข ์„ฑ๊ณต/์‹คํŒจ ๋ฉ”์‹œ์ง€ ํ‘œ์‹œ
โ€ข ์—๋Ÿฌ ๋ฐœ์ƒ ์‹œ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ• ์•ˆ๋‚ด
โ€ข ๊ถŒํ•œ ์š”์ฒญ ์‹œ ์ด์œ  ์„ค๋ช…

โ™ฟ ์ ‘๊ทผ์„ฑ ๊ณ ๋ ค
โ€ข ํ‚ค๋ณด๋“œ ๋„ค๋น„๊ฒŒ์ด์…˜ ์ง€์›
โ€ข ์Šคํฌ๋ฆฐ ๋ฆฌ๋” ํ˜ธํ™˜์„ฑ
โ€ข ์ถฉ๋ถ„ํ•œ ์ƒ‰์ƒ ๋Œ€๋น„

๐ŸŽฌ ๋งˆ๋ฌด๋ฆฌํ•˜๋ฉฐ

์™€, ์—ฌ๊ธฐ๊นŒ์ง€ ์ฝ์—ˆ๋‹ค๋ฉด ์ •๋ง ๋Œ€๋‹จํ•ด! ๐ŸŽ‰

๋ธŒ๋ผ์šฐ์ € API์™€ Web API๋Š” ์›น ๊ฐœ๋ฐœ์˜ ํ•ต์‹ฌ์ด์•ผ. ์ด ์นœ๊ตฌ๋“ค์„ ์ž˜ ํ™œ์šฉํ•˜๋ฉด ๋„ค์ดํ‹ฐ๋ธŒ ์•ฑ ๋ชป์ง€์•Š์€, ์•„๋‹ˆ ์–ด์ฉŒ๋ฉด ๋” ๋‚˜์€ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ์–ด.

๊ธฐ์–ตํ•ด, API๋Š” ๋„๊ตฌ์ผ ๋ฟ์ด์•ผ. ์ค‘์š”ํ•œ ๊ฑด ์ด ๋„๊ตฌ๋“ค์„ ์–ด๋–ป๊ฒŒ ์กฐํ•ฉํ•ด์„œ ์‚ฌ์šฉ์ž์—๊ฒŒ ๊ฐ€์น˜๋ฅผ ์ œ๊ณตํ•˜๋А๋ƒ์ง€. ๐Ÿ’Ž

์ฒ˜์Œ์—” ์–ด๋ ค์›Œ ๋ณด์ผ ์ˆ˜ ์žˆ์ง€๋งŒ, ํ•˜๋‚˜์”ฉ ์‹ค์Šตํ•˜๋‹ค ๋ณด๋ฉด ์–ด๋А์ƒˆ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์‚ฌ์šฉํ•˜๊ฒŒ ๋  ๊ฑฐ์•ผ. ์‹คํŒจ๋ฅผ ๋‘๋ ค์›Œํ•˜์ง€ ๋ง๊ณ , ๊ณ„์† ์‹คํ—˜ํ•˜๊ณ  ๋ฐฐ์›Œ๋‚˜๊ฐ€!

๊ทธ๋ฆฌ๊ณ  ํ˜น์‹œ ๋ง‰ํžˆ๋Š” ๋ถ€๋ถ„์ด ์žˆ๋‹ค๋ฉด? ๊ฐœ๋ฐœ์ž ์ปค๋ฎค๋‹ˆํ‹ฐ์— ์งˆ๋ฌธํ•˜๊ฑฐ๋‚˜, ๊ณต์‹ ๋ฌธ์„œ๋ฅผ ์ฐธ๊ณ ํ•˜๊ฑฐ๋‚˜, ์•„๋‹ˆ๋ฉด ์žฌ๋Šฅ๋„ท์—์„œ ์ „๋ฌธ๊ฐ€์˜ ๋„์›€์„ ๋ฐ›๋Š” ๊ฒƒ๋„ ์ข‹์€ ๋ฐฉ๋ฒ•์ด์•ผ! ๐Ÿ˜Š

๐Ÿš€ ์ด์ œ ๋‹น์‹  ์ฐจ๋ก€!

์˜ค๋Š˜ ๋ฐฐ์šด ๋‚ด์šฉ ์ค‘ ํ•˜๋‚˜๋ฅผ ๊ณจ๋ผ์„œ
์ง์ ‘ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด๋ณด์ž!

์ž‘์€ ํ”„๋กœ์ ํŠธ๋ผ๋„ ์™„์„ฑํ•˜๋Š” ๊ฒŒ ์ค‘์š”ํ•ด.

Happy Coding! ๐Ÿ’ปโœจ

์ด ๊ธ€์ด ๋„์›€์ด ๋˜์—ˆ๋‹ค๋ฉด, ๋‹ค๋ฅธ ๊ฐœ๋ฐœ์ž๋“ค๊ณผ๋„ ๊ณต์œ ํ•ด์ฃผ์„ธ์š”! ๐Ÿ“ข
๋” ๋งŽ์€ ๊ฐœ๋ฐœ ์ง€์‹์€ ์žฌ๋Šฅ๋„ท ์ง€์‹์ธ์˜ ์ˆฒ์—์„œ ๋งŒ๋‚˜๋ณด์„ธ์š”.

๐ŸŒฒ๐ŸŒณ๐ŸŒฒ

๋Œ“๊ธ€ ์ž‘์„ฑ

์ด ๊ธ€์— ๋Œ€ํ•œ ์—ฌ๋Ÿฌ๋ถ„์˜ ์ƒ๊ฐ์„ ๋“ค๋ ค์ฃผ์„ธ์š”

๋Œ“๊ธ€ 0
๋Œ“๊ธ€์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘์ž…๋‹ˆ๋‹ค.

์•„์ง ๋Œ“๊ธ€์ด ์—†์Šต๋‹ˆ๋‹ค.