๐Ÿงน 15. ๋ฉ”๋ชจ๋ฆฌ & ์„ฑ๋Šฅ โ€” ๊ฐ€๋น„์ง€ ์ปฌ๋ ‰ํ„ฐ๋ฅผ ์ดํ•ดํ•˜๋ฉด ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜๊ฐ€ ๋ณด์ธ๋‹ค

2026๋…„ 3์›” 8์ผ ์ˆ˜์ •๋จ

๐Ÿ“‹ ๊ฐœ์š”

๊ฐ€๋น„์ง€ ์ปฌ๋ ‰ํ„ฐ์˜ ์ž‘๋™ ์›๋ฆฌ, ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜ ํŒจํ„ด๊ณผ ํƒ์ง€๋ฒ•, V8 ์ตœ์ ํ™” ์›๋ฆฌ, Chrome DevTools ํ”„๋กœํŒŒ์ผ๋ง ์‹ค์ „ ์ „๋žต.

๐ŸŽฏ ์ด ์„น์…˜์„ ์ฝ๊ณ  ๋‚˜๋ฉด:

  • ๊ฐ€๋น„์ง€ ์ปฌ๋ ‰ํ„ฐ(GC)์˜ ๋„๋‹ฌ ๊ฐ€๋Šฅ์„ฑ(Reachability) ๊ธฐ๋ฐ˜ ๋™์ž‘ ์›๋ฆฌ๋ฅผ ์„ค๋ช…ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ, ํด๋กœ์ €, ํƒ€์ด๋จธ๋กœ ์ธํ•œ ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜๋ฅผ ์‹๋ณ„ํ•˜๊ณ  ์ˆ˜์ •ํ•œ๋‹ค.
  • Chrome DevTools Memory ํƒญ์œผ๋กœ ๋ฉ”๋ชจ๋ฆฌ ์Šค๋ƒ…์ƒท์„ ๋น„๊ตํ•ด ๋ˆ„์ˆ˜๋ฅผ ์ฐพ๋Š”๋‹ค.

๐Ÿ“‹ ๋ชฉ์ฐจ


๐Ÿ“Œ ์ด ๋ฌธ์„œ๋ฅผ ์ฝ๊ธฐ ์ „์—

โฑ๏ธ ์˜ˆ์ƒ ์ฝ๊ธฐ ์‹œ๊ฐ„: 20๋ถ„(์ „์ฒด) / ํ•ต์‹ฌ ํŒŒํŠธ๋งŒ: 12๋ถ„

๐Ÿ—บ๏ธ ์ด ๋ฌธ์„œ์˜ ํ๋ฆ„
[GC ์›๋ฆฌ] โ†’ [๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜ 4ํŒจํ„ด] โ†’ [DevTools ํƒ์ง€] โ†’ [์„ฑ๋Šฅ ์ตœ์ ํ™” ์›์น™]

๐ŸŽฏ ์ด ๋ฌธ์„œ๋ฅผ ๋‹ค ์ฝ์œผ๋ฉด ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ

  • "์™œ ์‹œ๊ฐ„์ด ์ง€๋‚ ์ˆ˜๋ก ์•ฑ์ด ๋А๋ ค์ง€์ง€?"์— ๋Œ€ํ•œ ๋‹ต์„ Memory ํƒญ์œผ๋กœ ํ™•์ธํ•œ๋‹ค.
  • ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ/ํƒ€์ด๋จธ ์ •๋ฆฌ ์ฝ”๋“œ๋ฅผ ๋น ๋œจ๋ฆฌ์ง€ ์•Š๋Š” ํŒจํ„ด์„ ์ž‘์„ฑํ•œ๋‹ค.
  • SPA์—์„œ ์ปดํฌ๋„ŒํŠธ ์–ธ๋งˆ์šดํŠธ ์‹œ ์ •๋ฆฌํ•ด์•ผ ํ•  ๋ชฉ๋ก์„ ์ฒดํฌ๋ฆฌ์ŠคํŠธ๋กœ ๊ด€๋ฆฌํ•œ๋‹ค.

๐Ÿ—บ๏ธ ์ด ๋ฌธ์„œ์˜ ๋ฐฐ๊ฒฝ ์„ธ๊ณ„๊ด€: '์˜์ˆ˜๋„ค ์ปค๋ฎค๋‹ˆํ‹ฐ'

๐Ÿฃ ์˜์ฒ : "์˜ํ˜ธ ๋‹˜, ์˜์ˆ˜๋„ค ์ปค๋ฎค๋‹ˆํ‹ฐ ์•ฑ์„ ์˜ค๋ž˜ ์“ฐ๋ฉด ์„œ์„œํžˆ ๋А๋ ค์ง€๊ณ , ํ•œ์ฐธ ์“ฐ๋‹ค ๋ณด๋ฉด ํƒญ์ด ์—„์ฒญ ๋ฌด๊ฑฐ์›Œ์ ธ์š”. ์ƒˆ๋กœ ๊ณ ์นจํ•˜๋ฉด ๋‹ค์‹œ ๋น ๋ฅธ๋ฐ... Chrome ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰ ๋ณด๋‹ˆ๊นŒ ๊ณ„์† ๋Š˜์–ด๋‚˜๊ณ  ์žˆ์–ด์š”. ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜์ธ ๊ฒƒ ๊ฐ™์€๋ฐ, ์–ด๋””์„œ ๋‚˜๋Š” ๊ฑด์ง€ ์–ด๋–ป๊ฒŒ ์ฐพ์ฃ ?"

๐Ÿฆ ์˜ํ˜ธ: "์ „ํ˜•์ ์ธ ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜ ์ฆ์ƒ์ด์•ผ. SPA์—์„œ ๋งŽ์ด ๋ฐœ์ƒํ•˜๋Š”๋ฐ, ์ฃผ๋ฒ”์€ ๋Œ€๋ถ€๋ถ„ โ‘  ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ ๋ฏธ์ œ๊ฑฐ โ‘ก setInterval ๋ฏธ์ œ๊ฑฐ โ‘ข ํด๋กœ์ €์— ๊ฐ‡ํžŒ ํฐ ๋ฐ์ดํ„ฐ โ‘ฃ ์ œ๊ฑฐ๋œ DOM์— ๋Œ€ํ•œ ์ฐธ์กฐ ์ค‘ ํ•˜๋‚˜์•ผ. Chrome DevTools Memory ํƒญ์—์„œ Heap Snapshot ์ฐ์–ด์„œ ๋น„๊ตํ•˜๋ฉด ๋ฒ”์ธ์„ ์žก์„ ์ˆ˜ ์žˆ์–ด."


๐Ÿค” 1. ์™œ ์•Œ์•„์•ผ ํ•˜๋Š”๊ฐ€?

์˜์ฒ ์ด๊ฐ€ ๊ฒช์—ˆ๋˜ "์‚ฌ์šฉํ• ์ˆ˜๋ก ๋А๋ ค์ง€๋Š” ์•ฑ"์˜ ์ฃผ๋ฒ”์€ ๋ฐ”๋กœ ๋ณด์ด์ง€ ์•Š๋Š” ๊ณณ์—์„œ ์ƒˆ๊ณ  ์žˆ๋Š” ๋ฉ”๋ชจ๋ฆฌ์˜€์Šต๋‹ˆ๋‹ค. ์˜ํ˜ธ ๋ฆฌ๋“œ ๋‹˜์€ "๊ฐ€๋น„์ง€ ์ปฌ๋ ‰ํ„ฐ๊ฐ€ ๋‹ค ์น˜์›Œ์ฃผ๊ฒ ์ง€"๋ผ๋Š” ์•ˆ์ผํ•œ ์ƒ๊ฐ์ด ๋ˆ„์ˆ˜์˜ ์‹œ์ž‘์ด๋ผ๊ณ  ๊ผฌ์ง‘์œผ์…จ์ฃ .

๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜๋Š” ์„œ๋ฒ„๋ฆฌ์Šค/SPA ํ™˜๊ฒฝ์—์„œ ๋” ์น˜๋ช…์ ์ด๋‹ค:

  • ๋ธŒ๋ผ์šฐ์ € ํƒญ์ด ์‹œ๊ฐ„์ด ์ง€๋‚ ์ˆ˜๋ก ๋А๋ ค์ง€๊ณ  ๊ฒฐ๊ตญ ํฌ๋ž˜์‹œ
  • Node.js ์„œ๋ฒ„๊ฐ€ ์„œ์„œํžˆ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ์žก์•„๋จน๋‹ค OOM์œผ๋กœ ์žฌ์‹œ์ž‘
  • ์‚ฌ์šฉ์ž๊ฐ€ ๋А๊ปด์ง€๋Š” ์„ฑ๋Šฅ ์ €ํ•˜ (์Šคํฌ๋กค ๋ฒ„๋ฒ…์ž„, ํด๋ฆญ ์ง€์—ฐ)

JS์˜ ์˜คํ•ด: "GC๊ฐ€ ์ž๋™์œผ๋กœ ๋ฉ”๋ชจ๋ฆฌ ๊ด€๋ฆฌํ•ด์ฃผ๋‹ˆ๊นŒ ์‹ ๊ฒฝ ์“ธ ํ•„์š” ์—†๋‹ค" โ†’ ํ‹€๋ ธ๋‹ค. GC๋Š” ๋„๋‹ฌ ๋ถˆ๊ฐ€๋Šฅํ•œ ๋ฉ”๋ชจ๋ฆฌ๋งŒ ์ˆ˜๊ฑฐํ•œ๋‹ค. ์‹ค์ˆ˜๋กœ ์ฐธ์กฐ๋ฅผ ์œ ์ง€ํ•˜๋ฉด GC๊ฐ€ ์ˆ˜๊ฑฐํ•˜์ง€ ๋ชปํ•œ๋‹ค.


โ™ป๏ธ 2. ๊ฐ€๋น„์ง€ ์ปฌ๋ ‰ํ„ฐ (Garbage Collector)

๋„๋‹ฌ ๊ฐ€๋Šฅ์„ฑ (Reachability)

๊ฐ€๋น„์ง€ ์ปฌ๋ ‰ํ„ฐ์˜ ๊ธฐ์ค€์€ ๋ช…ํ™•ํ•ฉ๋‹ˆ๋‹ค. "์ด ๋ฐ์ดํ„ฐ๊ฐ€ ๋ฟŒ๋ฆฌ(Root)๋กœ๋ถ€ํ„ฐ ์•„์ง ์—ฐ๊ฒฐ๋˜์–ด ์žˆ๋Š”๊ฐ€?" ๋งŒ์•ฝ ํ•œ ๋ฒˆ์ด๋ผ๋„ ์—ฐ๊ฒฐ์ด ๋Š๊ธฐ๋ฉด, ์ •์ฒ˜ ์—†๋Š” ๋ฐ์ดํ„ฐ๋“ค์€ ์ฒญ์†Œ ๋Œ€์ƒ์ด ๋ฉ๋‹ˆ๋‹ค.

// ๋ฉ”๋ชจ๋ฆฌ ํ• ๋‹น๊ณผ ํ•ด์ œ
 
// 1. ์ƒ์„ฑ โ€” ๋ฉ”๋ชจ๋ฆฌ ํ• ๋‹น
let post = { id: 1, title: "ํด๋กœ์ €", comments: Array(1000).fill("๋Œ“๊ธ€") };
 
// 2. ์‚ฌ์šฉ
console.log(post.title);
 
// 3. ๋„๋‹ฌ ๋ถˆ๊ฐ€๋Šฅ โ†’ GC ๋Œ€์ƒ
post = null; // post๊ฐ€ ๊ฐ€๋ฆฌํ‚ค๋˜ ๊ฐ์ฒด๋ฅผ ๋ฃจํŠธ์—์„œ ์ฐธ์กฐ ํ•ด์ œ
// GC๊ฐ€ ์‹คํ–‰๋  ๋•Œ ํ•ด๋‹น ๊ฐ์ฒด๋ฅผ ๋ฉ”๋ชจ๋ฆฌ์—์„œ ์ˆ˜๊ฑฐ
 
// ํด๋กœ์ €๋กœ ์ธํ•œ ์ฐธ์กฐ ์œ ์ง€
function createClosure() {
  const bigData = new Array(100000).fill("๋ฐ์ดํ„ฐ"); // ํฐ ๋ฐฐ์—ด
 
  return function () {
    return bigData.length; // bigData๋ฅผ ์ฐธ์กฐํ•˜๋Š” ํด๋กœ์ €
  };
}
 
const getCount = createClosure();
// getCount๊ฐ€ ์‚ด์•„์žˆ๋Š” ํ•œ, bigData๋„ GC ๋ถˆ๊ฐ€ (ํด๋กœ์ €๊ฐ€ ์ฐธ์กฐํ•˜๊ณ  ์žˆ์œผ๋ฏ€๋กœ)
// getCount = null; ํ•ด์•ผ bigData๋„ ์ˆ˜๊ฑฐ๋จ

Mark-and-Sweep ์•Œ๊ณ ๋ฆฌ์ฆ˜

ํ˜„๋Œ€ JS ์—”์ง„(V8 ๋“ฑ)์ด ์‚ฌ์šฉํ•˜๋Š” GC ๋ฐฉ์‹์ด๋‹ค. "์ฐธ์กฐ ํšŸ์ˆ˜(Reference Counting)"์™€ ๋‹ฌ๋ฆฌ ์ˆœํ™˜ ์ฐธ์กฐ๋„ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ฒ˜๋ฆฌํ•œ๋‹ค. ๋‘ ๋‹จ๊ณ„๋กœ ๋‚˜๋‰œ๋‹ค.

1๋‹จ๊ณ„ Mark (ํ‘œ์‹œ):
   ๋ฃจํŠธ์—์„œ ์‹œ์ž‘ํ•ด ๋„๋‹ฌ ๊ฐ€๋Šฅํ•œ ๋ชจ๋“  ๊ฐ์ฒด์— "์‚ด์•„์žˆ์Œ" ํ‘œ์‹œ

2๋‹จ๊ณ„ Sweep (์ˆ˜๊ฑฐ):
   ํ‘œ์‹œ๋˜์ง€ ์•Š์€ ๋ชจ๋“  ๊ฐ์ฒด๋ฅผ ๋ฉ”๋ชจ๋ฆฌ์—์„œ ํ•ด์ œ

V8์˜ ์ถ”๊ฐ€ ์ตœ์ ํ™”:
- Young/Old ์„ธ๋Œ€ ๋ถ„๋ฆฌ (Minor GC vs Major GC)
- ์ฆ๋ถ„(Incremental) GC โ€” ํ•œ ๋ฒˆ์— ์ „๋ถ€ ์ฒ˜๋ฆฌ ๋Œ€์‹  ์กฐ๊ธˆ์”ฉ
- ๋ณ‘๋ ฌ/๋™์‹œ GC โ€” GC ์ผ์‹œ ์ •์ง€ ์‹œ๊ฐ„ ์ตœ์†Œํ™”

๐Ÿ’ก V8์€ GC๊ฐ€ ์‹คํ–‰๋˜๋Š” ๋™์•ˆ JS ์‹คํ–‰์„ ์ž ์‹œ ๋ฉˆ์ถ˜๋‹ค(Stop-the-World). ๊ฐ์ฒด๊ฐ€ ๋งŽ์„์ˆ˜๋ก ์ด ์ •์ง€ ์‹œ๊ฐ„์ด ๊ธธ์–ด์ง€๊ธฐ ๋•Œ๋ฌธ์—, ๋ถˆํ•„์š”ํ•œ ๊ฐ์ฒด๋ฅผ ์˜ค๋ž˜ ๋ถ™๋“ค๊ณ  ์žˆ๋Š” ์ฝ”๋“œ๋Š” ์„ฑ๋Šฅ์— ์ง์ ‘์ ์ธ ์˜ํ–ฅ์„ ์ค€๋‹ค.


๐Ÿ’ง 3. ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜ ํŒจํ„ด 4๊ฐ€์ง€

์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ ๋ฏธ์ œ๊ฑฐ

๊ฐ€์žฅ ํ”ํ•œ ๋ˆ„์ˆ˜ ํŒจํ„ด์ž…๋‹ˆ๋‹ค. ์ฐฝ๋ฌธ์„ ์—ด์—ˆ์œผ๋ฉด(addListener) ๋‚˜๊ฐˆ ๋•Œ ๋‹ซ์•„์•ผ(removeListener) ํ•˜๋Š”๋ฐ, ์ด๋ฅผ ๊นœ๋นกํ•˜๋ฉด ๋ธŒ๋ผ์šฐ์ €๋Š” ์—ด๋ฆฐ ์ฐฝ๋ฌธ์œผ๋กœ ๊ณ„์† ๋ฐ”๋žŒ์„ ๋“ค์ด๋ถ“๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

// โŒ ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜ โ€” ๋ฆฌ์Šค๋„ˆ๊ฐ€ ๋ˆ„์ ๋จ
class PostCard extends React.Component {
  componentDidMount() {
    window.addEventListener("resize", this.handleResize);
    document.addEventListener("keydown", this.handleKeyDown);
  }
 
  // componentWillUnmount ์—†์Œ!
  // ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์–ธ๋งˆ์šดํŠธ๋˜์–ด๋„ ๋ฆฌ์Šค๋„ˆ๋Š” window/document์— ๋‚จ์•„์žˆ์Œ
  // ์žฌ๋งˆ์šดํŠธ๋  ๋•Œ๋งˆ๋‹ค ๋ฆฌ์Šค๋„ˆ๊ฐ€ ํ•˜๋‚˜์”ฉ ์ถ”๊ฐ€ โ†’ ๋ฌดํ•œ ๋ˆ„์ 
}
 
// โœ… ์ •๋ฆฌ (cleanup)
class PostCard extends React.Component {
  componentDidMount() {
    window.addEventListener("resize", this.handleResize);
  }
 
  componentWillUnmount() {
    window.removeEventListener("resize", this.handleResize); // ๋ฐ˜๋“œ์‹œ ์ œ๊ฑฐ
  }
}
 
// React ํ•จ์ˆ˜ ์ปดํฌ๋„ŒํŠธ โ€” useEffect cleanup
function PostCard() {
  useEffect(() => {
    const handleResize = () => { /* ... */ };
    window.addEventListener("resize", handleResize);
 
    return () => {
      window.removeEventListener("resize", handleResize); // cleanup
    };
  }, []); // ๋งˆ์šดํŠธ ์‹œ 1๋ฒˆ, ์–ธ๋งˆ์šดํŠธ ์‹œ cleanup
}

ํƒ€์ด๋จธ ๋ฏธ์ œ๊ฑฐ

setInterval์ด๋‚˜ setTimeout์€ ์šฐ๋ฆฌ๊ฐ€ ๋ฉˆ์ถ”๋ผ๊ณ  ํ•˜๊ธฐ ์ „๊นŒ์ง€๋Š” ์ ˆ๋Œ€ ์‰ฌ์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ด๋ฏธ ์‚ฌ๋ผ์ง„ ์œ ๋ น ์ปดํฌ๋„ŒํŠธ๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๊ธฐ ์œ„ํ•ด ๊ณ„์† ๋Œ์•„๊ฐ€๋ฉฐ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ๊ฐ‰์•„๋จน๋Š” ํƒ€์ด๋จธ๋“ค์„ ์กฐ์‹ฌํ•˜์„ธ์š”.

// โŒ setInterval ๋ฏธ์ œ๊ฑฐ
function startRealTimeUpdates() {
  setInterval(async () => {
    const newPosts = await fetchNewPosts();
    updateUI(newPosts);
  }, 5000);
  // ๋ฐ˜ํ™˜๋œ ํƒ€์ด๋จธ ID๋ฅผ ์ €์žฅํ•˜์ง€ ์•Š์Œ โ†’ ์ œ๊ฑฐ ๋ถˆ๊ฐ€
}
 
// โœ… ํƒ€์ด๋จธ ID ์ €์žฅ ํ›„ ์ œ๊ฑฐ
function startRealTimeUpdates() {
  const timerId = setInterval(async () => {
    const newPosts = await fetchNewPosts();
    updateUI(newPosts);
  }, 5000);
 
  return () => clearInterval(timerId); // cleanup ํ•จ์ˆ˜ ๋ฐ˜ํ™˜
}
 
// React useEffect์—์„œ
useEffect(() => {
  const timerId = setInterval(() => { /* ... */ }, 5000);
  return () => clearInterval(timerId); // ์–ธ๋งˆ์šดํŠธ ์‹œ ์ •๋ฆฌ
}, []);

ํด๋กœ์ € ๊ณผ๋‹ค ์บก์ฒ˜

ํด๋กœ์ €๋Š” ์ฃผ๋ณ€์˜ ๋ชจ๋“  ๊ฒƒ์„ ๊ธฐ์–ตํ•˜๋ ค ํ•ฉ๋‹ˆ๋‹ค. ๋ถˆํ•„์š”ํ•˜๊ฒŒ ๊ฑฐ๋Œ€ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ํ†ต์งธ๋กœ ๊ธฐ์–ตํ•ด๋ฒ„๋ฆฌ๋ฉด, ๊ทธ ๋ฐ์ดํ„ฐ๋Š” ์˜์›ํžˆ ๋ฉ”๋ชจ๋ฆฌ์˜ ํ•œ ์ž๋ฆฌ๋ฅผ ์ฐจ์ง€ํ•˜๊ฒŒ ๋˜์ฃ .

// โŒ ๊ฑฐ๋Œ€ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ํด๋กœ์ €๊ฐ€ ์˜ค๋ž˜ ๋ถ™๋“ค๊ณ  ์žˆ์Œ
function setupSearchHandler() {
  const allPosts = fetchAllPostsSync(); // ์ˆ˜๋งŒ ๊ฐœ์˜ ๊ฒŒ์‹œ๊ธ€ (์ˆ˜ MB)
 
  searchInput.addEventListener("input", (event) => {
    // allPosts ์ „์ฒด๋ฅผ ํด๋กœ์ €๋กœ ์บก์ฒ˜
    const results = allPosts.filter((p) =>
      p.title.includes(event.target.value)
    );
    renderResults(results);
  });
  // searchInput์ด DOM์— ์žˆ๋Š” ํ•œ, allPosts ์ „์ฒด๊ฐ€ ๋ฉ”๋ชจ๋ฆฌ์— ์œ ์ง€๋จ
}
 
// โœ… ํ•„์š”ํ•œ ๋ถ€๋ถ„๋งŒ ์ถ”์ถœํ•˜๊ฑฐ๋‚˜, ๊ฒ€์ƒ‰ ์ธ๋ฑ์Šค๋กœ ๊ฒฝ๋Ÿ‰ํ™”
function setupSearchHandler() {
  const allPosts = fetchAllPostsSync();
 
  // ๊ฒ€์ƒ‰์— ํ•„์š”ํ•œ ์ตœ์†Œ ์ •๋ณด๋งŒ ์ถ”์ถœ
  const searchIndex = allPosts.map(({ id, title, tags }) => ({
    id,
    title,
    tags,
  }));
 
  allPosts = null; // ์›๋ณธ ์ฐธ์กฐ ํ•ด์ œ โ€” GC ๊ฐ€๋Šฅ
 
  searchInput.addEventListener("input", (event) => {
    const results = searchIndex.filter((p) =>
      p.title.includes(event.target.value)
    );
    // ์ „์ฒด ๊ฒŒ์‹œ๊ธ€ ๋Œ€์‹  ๊ฒฝ๋Ÿ‰ํ™”๋œ ์ธ๋ฑ์Šค๋งŒ ์ฐธ์กฐ
    renderResults(results);
  });
}

DOM ์ฐธ์กฐ ๋ถ„๋ฆฌ

ํ™”๋ฉด(DOM)์—์„œ๋Š” ์ง€์›Œ์กŒ์ง€๋งŒ, ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ๋ณ€์ˆ˜๊ฐ€ ์—ฌ์ „ํžˆ ๊ฝ‰ ์ฅ๊ณ  ์žˆ๋Š” ์š”์†Œ๋“ค์ž…๋‹ˆ๋‹ค. ์ด๋ฅผ '๋ถ„๋ฆฌ๋œ DOM(Detached DOM)'์ด๋ผ๊ณ  ๋ถ€๋ฅด๋ฉฐ, ๋ณด์ด์ง€ ์•Š๋Š” ๊ณณ์—์„œ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ์ ์œ ํ•˜๋Š” ์œ ๋ น๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

// โŒ ์ œ๊ฑฐ๋œ DOM ์š”์†Œ๋ฅผ JS ๋ณ€์ˆ˜๊ฐ€ ์ฐธ์กฐํ•˜๊ณ  ์žˆ์Œ
let detachedNode;
 
function createAndStore() {
  const list = document.getElementById("post-list");
  detachedNode = list; // ์ „์—ญ ๋ณ€์ˆ˜์— ์ฐธ์กฐ ์ €์žฅ
 
  document.body.removeChild(list); // DOM์—์„œ ์ œ๊ฑฐํ–ˆ์ง€๋งŒ...
  // detachedNode๊ฐ€ ์—ฌ์ „ํžˆ ์ฐธ์กฐํ•˜๋ฏ€๋กœ GC ๋ถˆ๊ฐ€
  // โ†’ "๋ถ„๋ฆฌ๋œ DOM ํŠธ๋ฆฌ (Detached DOM Tree)"
}
 
// โœ… ์‚ฌ์šฉ ํ›„ ์ฐธ์กฐ ํ•ด์ œ
function createAndStore() {
  const list = document.getElementById("post-list");
  // ์‚ฌ์šฉ
  processListData(list);
  document.body.removeChild(list);
  // ์ง€์—ญ ๋ณ€์ˆ˜๋Š” ํ•จ์ˆ˜ ์ข…๋ฃŒ ์‹œ ์ž๋™ ํ•ด์ œ (์ „์—ญ ์ €์žฅ ์•ˆ ํ•จ)
}
 
// ํ•„์š”ํ•˜๋‹ค๋ฉด WeakRef ์‚ฌ์šฉ (ES2021)
let weakRef = new WeakRef(document.getElementById("post-list"));
// DOM์ด ์ œ๊ฑฐ๋˜๋ฉด WeakRef๋ฅผ ํ†ตํ•œ ์ฐธ์กฐ๋„ ์ž๋™์œผ๋กœ null
const elem = weakRef.deref(); // ์‚ด์•„์žˆ์œผ๋ฉด ์š”์†Œ, ์—†์œผ๋ฉด undefined

๐Ÿ” 4. ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜ ํƒ์ง€ โ€” Chrome DevTools

์˜ํ˜ธ ๋ฆฌ๋“œ ๋‹˜์ด ์˜์ฒ ์ด ์˜†์— ์•‰์•„์„œ ์ง์ ‘ DevTools๋ฅผ ์ผœ๋ณด์—ฌ์ค€ ๋ฐฉ๋ฒ•์ด๋‹ค. "์˜ค๋ Œ์ง€์ƒ‰์œผ๋กœ ํ‘œ์‹œ๋œ ๊ฒŒ Detached DOM Tree์•ผ. ์ €๊ฒŒ ์ฃผ๋ฒ”์ธ ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์•„." ์ด ๋ฐฉ๋ฒ•์„ ์ตํžˆ๋ฉด ๋ˆ„์ˆ˜๊ฐ€ ์–ด๋””์„œ ๋‚˜๋Š”์ง€ ๋ˆˆ์œผ๋กœ ์ง์ ‘ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

Heap Snapshot ๋น„๊ต ๋ฐฉ๋ฒ•:

  1. Chrome DevTools โ†’ Memory ํƒญ ์—ด๊ธฐ
  2. ์•ฑ์„ ์ •์ƒ ์ƒํƒœ์—์„œ "Heap snapshot" ๋ฒ„ํŠผ ํด๋ฆญ โ†’ Snapshot 1
  3. ๋ˆ„์ˆ˜๊ฐ€ ์˜์‹ฌ๋˜๋Š” ๋™์ž‘ ๋ฐ˜๋ณต (ํŽ˜์ด์ง€ ์ „ํ™˜, ์ปดํฌ๋„ŒํŠธ ๋งˆ์šดํŠธ/์–ธ๋งˆ์šดํŠธ ๋“ฑ)
  4. ๋˜ "Heap snapshot" โ†’ Snapshot 2
  5. Snapshot 2 ์„ ํƒ โ†’ Comparison ๋“œ๋กญ๋‹ค์šด์œผ๋กœ Snapshot 1๊ณผ ๋น„๊ต
  6. Delta (์ฆ๊ฐ€๋Ÿ‰) ๊ธฐ์ค€ ์ •๋ ฌ โ†’ ๊ณ„์† ์ฆ๊ฐ€ํ•˜๋Š” ๊ฐ์ฒด ํƒ€์ž…์ด ๋ˆ„์ˆ˜ ์ฃผ๋ฒ”

Timeline ๊ธฐ๋ก ๋ฐฉ๋ฒ•:

  1. Performance ํƒญ โ†’ Record ์‹œ์ž‘
  2. ์•ฑ ์‚ฌ์šฉ (์˜์‹ฌ ๋™์ž‘ ๋ฐ˜๋ณต)
  3. Record ์ค‘์ง€
  4. ์œ„์ชฝ ๋ฉ”๋ชจ๋ฆฌ ๊ทธ๋ž˜ํ”„๊ฐ€ ๊ณ„์† ์˜ค๋ฅด๋ฝ๋‚ด๋ฆฌ๋ฝํ•˜๋ฉด์„œ ํ•˜ํ•œ์„ ์ด ๋†’์•„์ง€๋ฉด ๋ˆ„์ˆ˜
// ๊ฐœ๋ฐœ ์ค‘ ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰ ์ง์ ‘ ํ™•์ธ
if (performance.memory) {
  const { usedJSHeapSize, totalJSHeapSize } = performance.memory;
  console.log(
    `ํž™ ์‚ฌ์šฉ: ${(usedJSHeapSize / 1024 / 1024).toFixed(1)}MB /` +
      `${(totalJSHeapSize / 1024 / 1024).toFixed(1)}MB`
  );
}

โšก 5. JS ์„ฑ๋Šฅ ์ตœ์ ํ™” ์›์น™

๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜๋ฅผ ๋ง‰๋Š” ๊ฒƒ์ด "๊ตฌ๋ฉ ๋ง‰๊ธฐ" ๋ผ๋ฉด, ์„ฑ๋Šฅ ์ตœ์ ํ™”๋Š” "์—”์ง„ ํ‚ค์šฐ๊ธฐ" ๋‹ค. ์•„๋ž˜ ๋‹ค์„ฏ ๊ฐ€์ง€ ์›์น™์€ ํ”„๋ก ํŠธ์—”๋“œ ์„ฑ๋Šฅ ๋ฉด์ ‘์—์„œ ๋‹จ๊ณจ๋กœ ๋“ฑ์žฅํ•˜๋ฉฐ, ๋™์‹œ์— ์‹ค๋ฌด์—์„œ ์ฆ‰์‹œ ์ ์šฉ ๊ฐ€๋Šฅํ•œ ์ฒดํฌ๋ฆฌ์ŠคํŠธ์ด๊ธฐ๋„ ํ•˜๋‹ค. ๋ฃจํ”„ ์ตœ์ ํ™”๋ถ€ํ„ฐ ๊ฐ์ฒด ํ’€๋ง๊นŒ์ง€ โ€” ๊ฐ๊ฐ ์™œ ๋น ๋ฅธ์ง€ ์›๋ฆฌ๊นŒ์ง€ ํ•จ๊ป˜ ๊ธฐ์–ตํ•ด ๋‘์ž.

// 1. ๋ฃจํ”„ ์ตœ์ ํ™” โ€” ๋ฐฐ์—ด ๊ธธ์ด๋ฅผ ๋งค๋ฒˆ ๊ณ„์‚ฐํ•˜์ง€ ๋ง ๊ฒƒ
// โŒ ๋งค ์ดํ„ฐ๋ ˆ์ด์…˜๋งˆ๋‹ค posts.length ์ ‘๊ทผ
for (let i = 0; i < posts.length; i++) { ... }
 
// โœ… ๋ณ€์ˆ˜๋กœ ์บ์‹ฑ
const len = posts.length;
for (let i = 0; i < len; i++) { ... }
 
// 2. DOM ์ ‘๊ทผ ์ตœ์†Œํ™” โ€” ๋ฆฌํ”Œ๋กœ์šฐ/๋ฆฌํŽ˜์ธํŠธ ์ค„์ด๊ธฐ
// โŒ DOM์„ ๋ฃจํ”„ ์•ˆ์—์„œ ๋ฐ˜๋ณต ์ ‘๊ทผ
for (let i = 0; i < 100; i++) {
  document.getElementById("counter").textContent = i; // ๋งค ๋ฃจํ”„๋งˆ๋‹ค ๋ฆฌํ”Œ๋กœ์šฐ
}
 
// โœ… DOM ์—…๋ฐ์ดํŠธ ํ•œ ๋ฒˆ์— ๋ชจ์•„์„œ
const counter = document.getElementById("counter");
let text = "";
for (let i = 0; i < 100; i++) {
  text = i.toString(); // ๊ณ„์‚ฐ์€ ๋ฉ”๋ชจ๋ฆฌ์—์„œ
}
counter.textContent = text; // DOM์€ ํ•œ ๋ฒˆ๋งŒ
 
// 3. DocumentFragment โ€” ๋Œ€๋Ÿ‰ DOM ์‚ฝ์ž… ์ตœ์ ํ™”
// โŒ ํ•˜๋‚˜์”ฉ ์ถ”๊ฐ€ โ€” 100๋ฒˆ ๋ฆฌํ”Œ๋กœ์šฐ
const list = document.getElementById("post-list");
posts.forEach((post) => {
  const li = document.createElement("li");
  li.textContent = post.title;
  list.appendChild(li); // ๋งค๋ฒˆ ๋ฆฌํ”Œ๋กœ์šฐ
});
 
// โœ… DocumentFragment๋กœ ๋ฌถ์–ด์„œ ํ•œ ๋ฒˆ์—
const fragment = document.createDocumentFragment();
posts.forEach((post) => {
  const li = document.createElement("li");
  li.textContent = post.title;
  fragment.appendChild(li); // ์‹ค์ œ DOM์— ์—†์Œ โ†’ ๋ฆฌํ”Œ๋กœ์šฐ ์—†์Œ
});
list.appendChild(fragment); // ํ•œ ๋ฒˆ๋งŒ ๋ฆฌํ”Œ๋กœ์šฐ
 
// 4. ๋””๋ฐ”์šด์Šค/์Šค๋กœํ‹€ โ€” ์ด๋ฒคํŠธ ์ตœ์ ํ™”
function debounce(fn, delay) {
  let timer;
  return function (...args) {
    clearTimeout(timer);
    timer = setTimeout(() => fn.apply(this, args), delay);
  };
}
 
function throttle(fn, limit) {
  let lastTime = 0;
  return function (...args) {
    const now = Date.now();
    if (now - lastTime >= limit) {
      lastTime = now;
      return fn.apply(this, args);
    }
  };
}
 
// ๊ฒ€์ƒ‰ ์ž…๋ ฅ โ€” ํƒ€์ดํ•‘ ๋ฉˆ์ถ˜ ํ›„ 300ms ๋’ค์—๋งŒ ์‹คํ–‰
searchInput.addEventListener("input", debounce(handleSearch, 300));
 
// ์Šคํฌ๋กค ์ด๋ฒคํŠธ โ€” 100ms์— ํ•œ ๋ฒˆ๋งŒ ์‹คํ–‰
window.addEventListener("scroll", throttle(handleScroll, 100));
 
// 5. ๊ฐ์ฒด ํ’€๋ง (Object Pooling) โ€” ์žฆ์€ ๊ฐ์ฒด ์ƒ์„ฑ ๋Œ€์‹  ์žฌ์‚ฌ์šฉ
class ObjectPool {
  #pool = [];
  #factory;
 
  constructor(factory) {
    this.#factory = factory;
  }
 
  acquire() {
    return this.#pool.pop() ?? this.#factory();
  }
 
  release(obj) {
    // ์ดˆ๊ธฐํ™” ํ›„ ํ’€์— ๋ฐ˜ํ™˜
    Object.keys(obj).forEach((key) => delete obj[key]);
    this.#pool.push(obj);
  }
}
 
// ๊ฒŒ์ž„, ์‹ค์‹œ๊ฐ„ ์•ฑ์—์„œ ์œ ์šฉ
const particlePool = new ObjectPool(() => ({ x: 0, y: 0, velocity: 0 }));

๐Ÿ“ ๋งˆ๋ฌด๋ฆฌ ํ€ด์ฆˆ

Q1. GC๊ฐ€ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ์ˆ˜๊ฑฐํ•˜๋Š” ๊ธฐ์ค€์€ ๋ฌด์—‡์ธ๊ฐ€? "์ฐธ์กฐ ํšŸ์ˆ˜(Reference Counting)"์™€ ํ˜„์žฌ ๋ฐฉ์‹์˜ ์ฐจ์ด๋ฅผ ์„ค๋ช…ํ•˜๋ผ.

โœ… ์ •๋‹ต: ํ˜„๋Œ€ GC๋Š” ๋„๋‹ฌ ๊ฐ€๋Šฅ์„ฑ(Reachability) ๊ธฐ๋ฐ˜ Mark-and-Sweep. ์ฐธ์กฐ ํšŸ์ˆ˜ ๋ฐฉ์‹์€ ์ˆœํ™˜ ์ฐธ์กฐ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜์ง€ ๋ชปํ•œ๋‹ค.

๐Ÿ’ก ์ƒ์„ธ ํ•ด์„ค:

// ์ˆœํ™˜ ์ฐธ์กฐ โ€” Reference Counting์˜ ์ทจ์•ฝ์ 
function createCycle() {
  const a = {};
  const b = {};
  a.ref = b; // a โ†’ b ์ฐธ์กฐ
  b.ref = a; // b โ†’ a ์ฐธ์กฐ (์ˆœํ™˜!)
  // ํ•จ์ˆ˜ ์ข…๋ฃŒ โ†’ a, b ์ง€์—ญ ๋ณ€์ˆ˜ ํ•ด์ œ
  // ํ•˜์ง€๋งŒ a์™€ b๋Š” ์„œ๋กœ๋ฅผ ์ฐธ์กฐํ•˜๋ฏ€๋กœ ์ฐธ์กฐ ํšŸ์ˆ˜๊ฐ€ 0์ด ์•ˆ ๋จ
  // Reference Counting: ์ˆ˜๊ฑฐ ๋ถˆ๊ฐ€ โ†’ ๋ˆ„์ˆ˜
  // Mark-and-Sweep: ๋ฃจํŠธ์—์„œ ๋„๋‹ฌ ๋ถˆ๊ฐ€ โ†’ ์ˆ˜๊ฑฐ ๊ฐ€๋Šฅ โœ…
}
  • ํ˜„๋Œ€ V8์€ Mark-and-Sweep ๊ธฐ๋ฐ˜์ด๋ฏ€๋กœ ์ˆœํ™˜ ์ฐธ์กฐ๋Š” ์ž๋™์œผ๋กœ ์ˆ˜๊ฑฐ๋œ๋‹ค.
  • ๐Ÿ“Œ ํ•ต์‹ฌ ๊ธฐ์–ต๋ฒ•: "GC๋Š” '๋ฃจํŠธ์—์„œ ๋‹ฟ์„ ์ˆ˜ ์žˆ๋ƒ'๋งŒ ๋”ฐ์ง„๋‹ค. ์•„๋ฌด๋„ ์—ฐ๊ฒฐํ•ด์ฃผ์ง€ ์•Š์œผ๋ฉด ์„ฌ์ฒ˜๋Ÿผ ๊ณ ๋ฆฝ๋œ ๊ฐ์ฒด๋“ค๋„ ๊ฐ™์ด ์ˆ˜๊ฑฐ๋œ๋‹ค."

Q2. React ํ•จ์ˆ˜ ์ปดํฌ๋„ŒํŠธ์—์„œ ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜๊ฐ€ ์ž์ฃผ ๋ฐœ์ƒํ•˜๋Š” ์ƒํ™ฉ 2๊ฐ€์ง€์™€ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์€?

โœ… ์ •๋‹ต: โ‘  useEffect ์•ˆ์—์„œ ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ/ํƒ€์ด๋จธ ์ถ”๊ฐ€ ํ›„ cleanup ๋ฏธ๋ฐ˜ํ™˜ โ‘ก async ์š”์ฒญ์ด ์–ธ๋งˆ์šดํŠธ ํ›„ state ์—…๋ฐ์ดํŠธ ์‹œ๋„

๐Ÿ’ก ์ƒ์„ธ ํ•ด์„ค:

// โ‘  ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ ๋ˆ„์ˆ˜
useEffect(() => {
  window.addEventListener("resize", handleResize);
  return () => window.removeEventListener("resize", handleResize); // cleanup ํ•„์ˆ˜
}, []);
 
// โ‘ก ์–ธ๋งˆ์šดํŠธ ํ›„ setState ๊ฒฝ๊ณ  (React 18์—์„œ๋Š” ๊ฒฝ๊ณ  ์ œ๊ฑฐ๋์ง€๋งŒ ์—ฌ์ „ํžˆ ๋ถˆํ•„์š”ํ•œ ์ž‘์—…)
useEffect(() => {
  let isActive = true;
  fetchPost(postId).then((post) => {
    if (isActive) setPost(post); // ์–ธ๋งˆ์šดํŠธ๋์œผ๋ฉด ์Šคํ‚ต
  });
  return () => { isActive = false; }; // cleanup
}, [postId]);
 
// ๋” ์ข‹์€ ๋ฐฉ๋ฒ•: AbortController
useEffect(() => {
  const controller = new AbortController();
  fetchPost(postId, controller.signal).then(setPost).catch(() => {});
  return () => controller.abort();
}, [postId]);
  • ๐Ÿ“Œ ํ•ต์‹ฌ ๊ธฐ์–ต๋ฒ•: "useEffect์—์„œ ๋ฌด์–ธ๊ฐ€๋ฅผ '์ผฐ๋‹ค๋ฉด', cleanup์—์„œ ๋ฐ˜๋“œ์‹œ '๊บผ์•ผ' ํ•œ๋‹ค. ์ผœ๊ณ  ๋„๋Š” ๋Œ€์นญ์„ ํ•ญ์ƒ ์ง€์ผœ๋ผ."

Q3. ์˜์ฒ ์ด์˜ ํ…Œ์ŠคํŠธ ํƒ€์ž„ โ€” ์˜์ˆ™ ๋””์ž์ด๋„ˆ์˜ UX ๋ฆฌ๋ทฐ

์˜์ˆ™ ๋‹˜์ด ํ”ผ๋“œ๋ฐฑ์„ ์ฃผ์…จ์Šต๋‹ˆ๋‹ค. "๊ฒŒ์‹œ๊ธ€ ํ”ผ๋“œ๋ฅผ ์Šคํฌ๋กคํ•  ๋•Œ ํ™”๋ฉด์ด ํˆญํˆญ ๋Š๊ธฐ๋Š” ๋А๋‚Œ์ด ๋“ค์–ด์š”. ํŠนํžˆ ์Šคํฌ๋กคํ•  ๋•Œ๋งˆ๋‹ค ์ƒˆ ๊ฒŒ์‹œ๊ธ€์ด ๊นœ๋นกํ•˜๋ฉฐ ๋‚˜ํƒ€๋‚˜๋Š” ๊ฒŒ ์กฐ๊ธˆ ์–ด์ƒ‰ํ•œ๋ฐ, ๋ถ€๋“œ๋Ÿฝ๊ฒŒ ๊ฐœ์„ ํ•  ์ˆ˜ ์žˆ์„๊นŒ์š”?"

์˜์ฒ ์ด์˜ ์ฝ”๋“œ๋Š” ์Šคํฌ๋กค๋งˆ๋‹ค ์ƒˆ ๊ฒŒ์‹œ๊ธ€์„ DOM์— ํ•˜๋‚˜์”ฉ ์ถ”๊ฐ€ํ•˜๊ณ  ์žˆ๋‹ค. ์–ด๋–ป๊ฒŒ ๊ฐœ์„ ํ•ด์•ผ ํ•˜๋Š”๊ฐ€?

โœ… ์ •๋‹ต: ์Šค๋กœํ‹€๋กœ ์Šคํฌ๋กค ์ด๋ฒคํŠธ throttle, DocumentFragment๋กœ DOM ๋ฐฐ์น˜ ์‚ฝ์ž…, ์ด๋ฏธ ํ‘œ์‹œ๋œ ํ•ญ๋ชฉ ์ค‘๋ณต ์ถ”๊ฐ€ ๋ฐฉ์ง€.

๐Ÿ’ก ์ƒ์„ธ ํ•ด์„ค:

// โœ… ๊ฐœ์„ ๋œ ๋ฌดํ•œ ์Šคํฌ๋กค ๊ตฌํ˜„
const renderedPostIds = new Set(); // ์ค‘๋ณต ๋ฐฉ์ง€ (Set.has โ†’ O(1))
let isLoading = false;
 
const handleScroll = throttle(async () => {
  const { scrollTop, scrollHeight, clientHeight } = document.documentElement;
  const isNearBottom = scrollTop + clientHeight >= scrollHeight - 200;
 
  if (!isNearBottom || isLoading) return;
 
  isLoading = true;
  try {
    const newPosts = await fetchNextPage();
 
    // DocumentFragment๋กœ ๋ฐฐ์น˜ ์‚ฝ์ž…
    const fragment = document.createDocumentFragment();
    newPosts
      .filter((p) => !renderedPostIds.has(p.id)) // ์ค‘๋ณต ์ œ๊ฑฐ
      .forEach((post) => {
        renderedPostIds.add(post.id);
        const el = createPostElement(post);
        fragment.appendChild(el);
      });
 
    postList.appendChild(fragment); // ํ•œ ๋ฒˆ์— DOM ๋ฐ˜์˜
  } finally {
    isLoading = false;
  }
}, 200); // 200ms์— ํ•œ ๋ฒˆ๋งŒ ์‹คํ–‰
 
window.addEventListener("scroll", handleScroll);
  • ๐Ÿ“Œ ํ•ต์‹ฌ ๊ธฐ์–ต๋ฒ•: "์Šคํฌ๋กค ์ด๋ฒคํŠธ๋Š” throttle, DOM ๋ฐฐ์น˜ ์‚ฝ์ž…์€ DocumentFragment, ์ค‘๋ณต ์ฒดํฌ๋Š” Set. ์ด ์„ธ ๊ฐ€์ง€๊ฐ€ ๋ฌดํ•œ ์Šคํฌ๋กค์˜ ์‚ผ์ด์‚ฌ๋‹ค."

๐Ÿฃ ์˜์ฒ ์ด์˜ ํ‡ด๊ทผ ์ผ๊ธฐ

๋“œ๋””์–ด JS ๊ฐ€์ด๋“œ ๋งˆ์ง€๋ง‰ ์ฑ•ํ„ฐ๋‹ค. ์˜ค๋Š˜์€ ๋ฉ”๋ชจ๋ฆฌ์™€ ์„ฑ๋Šฅ โ€” ์†”์งํžˆ ์ฒ˜์Œ์—๋Š” "์ด๋Ÿฐ ๊ฒƒ๊นŒ์ง€ ์•Œ์•„์•ผ ํ•ด?" ์‹ถ์—ˆ๋Š”๋ฐ, ์˜์ˆ˜๋„ค ์ปค๋ฎค๋‹ˆํ‹ฐ ์•ฑ์ด ์„œ์„œํžˆ ๋А๋ ค์ง€๋Š” ๋ฒ„๊ทธ๊ฐ€ ์‹ค์ œ๋กœ ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ ๋ˆ„์ˆ˜ ๋•Œ๋ฌธ์ด์—ˆ๋‹ค๋Š” ๊ฑธ Chrome DevTools๋กœ ์ง์ ‘ ํ™•์ธํ•˜๊ณ  ๋‚˜์„œ ์™„์ „ํžˆ ํƒœ๋„๊ฐ€ ๋ฐ”๋€Œ์—ˆ๋‹ค.

๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜๋Š” ๊ธฐ๋Šฅ ๋ฒ„๊ทธ์ฒ˜๋Ÿผ "์ด ๋ฒ„ํŠผ์ด ์•ˆ ๋ˆŒ๋ฆฐ๋‹ค" ๊ฐ™์€ ๋ช…ํ™•ํ•œ ์ฆ์ƒ์ด ์—†๋‹ค. ๊ทธ๋ƒฅ ์„œ์„œํžˆ ๋А๋ ค์ง€๋‹ค๊ฐ€ ์–ด๋А ๋‚  ํƒญ์ด ์ฃฝ์–ด๋ฒ„๋ฆฐ๋‹ค. ๊ทธ๊ฒŒ ๋” ๋ฌด์„ญ๋‹ค๊ณ  ์˜ํ˜ธ ๋‹˜์ด ๋งํ–ˆ๋Š”๋ฐ, ์ง„์งœ ๊ทธ ๋ง์ด ์™€๋‹ฟ์•˜๋‹ค.

๐Ÿ’ก ์˜ค๋Š˜์˜ ๊ตํ›ˆ: "๊ฐ€๋น„์ง€ ์ปฌ๋ ‰ํ„ฐ๋Š” ๋ฏฟ์Œ์งํ•œ ์ฒญ์†Œ๋ถ€์ง€๋งŒ, ์ฃผ์ธ์ด ๊ผญ ์ฅ๊ณ  ์žˆ๋Š” ๋ฌผ๊ฑด๊นŒ์ง€๋Š” ๋งˆ์Œ๋Œ€๋กœ ์น˜์šฐ์ง€ ๋ชปํ•ฉ๋‹ˆ๋‹ค. 'ํ•„์š”ํ•  ๋•Œ ์“ฐ๊ณ , ๋ถˆํ•„์š”ํ•ด์ง€๋ฉด ๋ฐ˜๋“œ์‹œ ๋†“์•„์ฃผ๋Š”' ์ฒญ์†Œ์˜ ๊ธฐ๋ณธ์„ ์ฝ”๋“œ์—๋„ ์ ์šฉํ•ด ๋ณด์„ธ์š”. ๊ทธ๊ฒƒ์ด ์พŒ์ ํ•œ UX๋ฅผ ๋งŒ๋“œ๋Š” ์ฒซ๊ฑธ์Œ์ž…๋‹ˆ๋‹ค."

15๊ฐœ ์ฑ•ํ„ฐ ์™„์ฃผ! ์†”์งํžˆ ํž˜๋“ค์—ˆ๋Š”๋ฐ, ๋๋‚ด๊ณ  ๋‚˜๋‹ˆ๊นŒ ๋ฟŒ๋“ฏํ•˜๋‹ค. ์‹คํ–‰ ์ปจํ…์ŠคํŠธ๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ด์„œ ๋ฉ”๋ชจ๋ฆฌ ๊ด€๋ฆฌ๊นŒ์ง€ โ€” ์ด ๊ฐ€์ด๋“œ๋“ค์„ ๋‹ค ํก์ˆ˜ํ•˜๋ฉด ๋ฉด์ ‘์—์„œ๋„, ์‹ค๋ฌด์—์„œ๋„ ์ž์‹  ์žˆ๊ฒŒ ๋งํ•  ์ˆ˜ ์žˆ์„ ๊ฒƒ ๊ฐ™๋‹ค. ์˜ํ˜ธ ๋‹˜ํ•œํ…Œ ์˜ค๋Š˜ ๋ง›์žˆ๋Š” ๊ฑฐ ์–ป์–ด๋จน์–ด์•ผ์ง€. ์—ด์‹ฌํžˆ ๊ณต๋ถ€ํ–ˆ์œผ๋‹ˆ๊นŒ ๋‹น์—ฐํžˆ ์‚ฌ์ค˜์•ผ ํ•˜์ง€ ์•Š๊ฒ ์–ด์š”? ๐Ÿ˜„


๐Ÿ”— ๋” ์•Œ์•„๋ณด๊ธฐ