๐งน 15. ๋ฉ๋ชจ๋ฆฌ & ์ฑ๋ฅ โ ๊ฐ๋น์ง ์ปฌ๋ ํฐ๋ฅผ ์ดํดํ๋ฉด ๋ฉ๋ชจ๋ฆฌ ๋์๊ฐ ๋ณด์ธ๋ค
๐ ๊ฐ์
๊ฐ๋น์ง ์ปฌ๋ ํฐ์ ์๋ ์๋ฆฌ, ๋ฉ๋ชจ๋ฆฌ ๋์ ํจํด๊ณผ ํ์ง๋ฒ, V8 ์ต์ ํ ์๋ฆฌ, Chrome DevTools ํ๋กํ์ผ๋ง ์ค์ ์ ๋ต.
๐ฏ ์ด ์น์ ์ ์ฝ๊ณ ๋๋ฉด:
- ๊ฐ๋น์ง ์ปฌ๋ ํฐ(GC)์ ๋๋ฌ ๊ฐ๋ฅ์ฑ(Reachability) ๊ธฐ๋ฐ ๋์ ์๋ฆฌ๋ฅผ ์ค๋ช ํ ์ ์๋ค.
- ์ด๋ฒคํธ ๋ฆฌ์ค๋, ํด๋ก์ , ํ์ด๋จธ๋ก ์ธํ ๋ฉ๋ชจ๋ฆฌ ๋์๋ฅผ ์๋ณํ๊ณ ์์ ํ๋ค.
- Chrome DevTools Memory ํญ์ผ๋ก ๋ฉ๋ชจ๋ฆฌ ์ค๋ ์ท์ ๋น๊ตํด ๋์๋ฅผ ์ฐพ๋๋ค.
๐ ๋ชฉ์ฐจ
- ๐ ์ด ๋ฌธ์๋ฅผ ์ฝ๊ธฐ ์ ์
- ๐ค 1. ์ ์์์ผ ํ๋๊ฐ?
- โป๏ธ 2. ๊ฐ๋น์ง ์ปฌ๋ ํฐ (Garbage Collector)
- ๐ง 3. ๋ฉ๋ชจ๋ฆฌ ๋์ ํจํด 4๊ฐ์ง
- ๐ 4. ๋ฉ๋ชจ๋ฆฌ ๋์ ํ์ง โ Chrome DevTools
- โก 5. JS ์ฑ๋ฅ ์ต์ ํ ์์น
- ๐ ๋ง๋ฌด๋ฆฌ ํด์ฆ
- ๐ฃ ์์ฒ ์ด์ ํด๊ทผ ์ผ๊ธฐ
- ๐ ๋ ์์๋ณด๊ธฐ
๐ ์ด ๋ฌธ์๋ฅผ ์ฝ๊ธฐ ์ ์
โฑ๏ธ ์์ ์ฝ๊ธฐ ์๊ฐ: 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 ๋น๊ต ๋ฐฉ๋ฒ:
- Chrome DevTools โ Memory ํญ ์ด๊ธฐ
- ์ฑ์ ์ ์ ์ํ์์ "Heap snapshot" ๋ฒํผ ํด๋ฆญ โ Snapshot 1
- ๋์๊ฐ ์์ฌ๋๋ ๋์ ๋ฐ๋ณต (ํ์ด์ง ์ ํ, ์ปดํฌ๋ํธ ๋ง์ดํธ/์ธ๋ง์ดํธ ๋ฑ)
- ๋ "Heap snapshot" โ Snapshot 2
- Snapshot 2 ์ ํ โ Comparison ๋๋กญ๋ค์ด์ผ๋ก Snapshot 1๊ณผ ๋น๊ต
- Delta (์ฆ๊ฐ๋) ๊ธฐ์ค ์ ๋ ฌ โ ๊ณ์ ์ฆ๊ฐํ๋ ๊ฐ์ฒด ํ์ ์ด ๋์ ์ฃผ๋ฒ
Timeline ๊ธฐ๋ก ๋ฐฉ๋ฒ:
- Performance ํญ โ Record ์์
- ์ฑ ์ฌ์ฉ (์์ฌ ๋์ ๋ฐ๋ณต)
- Record ์ค์ง
- ์์ชฝ ๋ฉ๋ชจ๋ฆฌ ๊ทธ๋ํ๊ฐ ๊ณ์ ์ค๋ฅด๋ฝ๋ด๋ฆฌ๋ฝํ๋ฉด์ ํํ์ ์ด ๋์์ง๋ฉด ๋์
// ๊ฐ๋ฐ ์ค ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋ ์ง์ ํ์ธ
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๊ฐ ์ฑํฐ ์์ฃผ! ์์งํ ํ๋ค์๋๋ฐ, ๋๋ด๊ณ ๋๋๊น ๋ฟ๋ฏํ๋ค. ์คํ ์ปจํ ์คํธ๋ถํฐ ์์ํด์ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ๊น์ง โ ์ด ๊ฐ์ด๋๋ค์ ๋ค ํก์ํ๋ฉด ๋ฉด์ ์์๋, ์ค๋ฌด์์๋ ์์ ์๊ฒ ๋งํ ์ ์์ ๊ฒ ๊ฐ๋ค. ์ํธ ๋ํํ ์ค๋ ๋ง์๋ ๊ฑฐ ์ป์ด๋จน์ด์ผ์ง. ์ด์ฌํ ๊ณต๋ถํ์ผ๋๊น ๋น์ฐํ ์ฌ์ค์ผ ํ์ง ์๊ฒ ์ด์? ๐