Skip to content

Commit b04d761

Browse files
0 parents  commit b04d761

197 files changed

Lines changed: 50830 additions & 0 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.nojekyll‎

Whitespace-only changes.
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
<!doctype html>
2+
<html lang="ru">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<title>Memory leak demo with setTimeout</title>
6+
7+
<style>
8+
body {
9+
font-family: system-ui, sans-serif;
10+
padding: 24px;
11+
}
12+
13+
button {
14+
margin: 4px;
15+
padding: 8px 12px;
16+
cursor: pointer;
17+
}
18+
19+
#app {
20+
margin-top: 24px;
21+
display: grid;
22+
gap: 8px;
23+
}
24+
25+
.card {
26+
padding: 12px 16px;
27+
border: 1px solid #ccc;
28+
border-radius: 8px;
29+
background: #f8f8f8;
30+
transition:
31+
opacity 300ms ease,
32+
transform 300ms ease;
33+
}
34+
35+
.card.removing {
36+
opacity: 0;
37+
transform: translateX(24px);
38+
}
39+
40+
.leaky {
41+
border-color: #e53935;
42+
background: #fff5f5;
43+
}
44+
45+
.clean {
46+
border-color: #43a047;
47+
background: #f4fff4;
48+
}
49+
50+
.info {
51+
margin-top: 16px;
52+
color: #555;
53+
line-height: 1.5;
54+
}
55+
56+
.counter {
57+
margin-top: 16px;
58+
font-weight: 600;
59+
}
60+
</style>
61+
</head>
62+
<body>
63+
<h1>Memory leak demo</h1>
64+
65+
<button onclick="createLeakyCard()">Create leaky card</button>
66+
<button onclick="createManyLeakyCards()">Create 20 leaky cards</button>
67+
<button onclick="createCleanCard()">Create clean card</button>
68+
<button onclick="createManyCleanCards()">Create 20 clean cards</button>
69+
<button onclick="clearLeaks()">Clear leaks</button>
70+
71+
<div class="counter">
72+
Leaks count:
73+
<span id="leaks-count">0</span>
74+
</div>
75+
76+
<div class="info">
77+
Красные карточки удаляются из DOM, но остаются в памяти из-за ссылки в
78+
<code>window.__leaks</code>
79+
.
80+
<br />
81+
Зеленые карточки удаляются из DOM и могут быть очищены GC.
82+
</div>
83+
84+
<div id="app"></div>
85+
86+
<script>
87+
window.__leaks = [];
88+
89+
let counter = 0;
90+
91+
const app = document.querySelector('#app');
92+
const leaksCount = document.querySelector('#leaks-count');
93+
94+
function updateLeaksCount() {
95+
leaksCount.textContent = String(window.__leaks.length);
96+
}
97+
98+
function createBigPayload(id) {
99+
return Array.from({length: 1000}, (_, index) => ({
100+
id,
101+
index,
102+
text: `large-payload-${id}-${index}-${'x'.repeat(200)}`,
103+
}));
104+
}
105+
106+
function removeWithAnimation(node, afterRemove) {
107+
setTimeout(() => {
108+
node.classList.add('removing');
109+
110+
setTimeout(() => {
111+
node.remove();
112+
113+
if (afterRemove) {
114+
afterRemove();
115+
}
116+
}, 300);
117+
}, 1000);
118+
}
119+
120+
function createLeakyCard() {
121+
counter++;
122+
123+
const id = counter;
124+
const node = document.createElement('div');
125+
const payload = createBigPayload(id);
126+
127+
node.className = 'card leaky';
128+
node.textContent = `Leaky card #${id}: I will disappear from DOM, but stay in memory`;
129+
130+
function handleClick() {
131+
console.log('Leaky payload is still alive:', payload[0]);
132+
}
133+
134+
node.addEventListener('click', handleClick);
135+
136+
app.append(node);
137+
138+
removeWithAnimation(node, () => {
139+
/**
140+
* Вот здесь создается утечка.
141+
*
142+
* Карточка уже удалена из DOM,
143+
* но ссылка на нее сохраняется в глобальном массиве.
144+
*/
145+
window.__leaks.push({
146+
node,
147+
payload,
148+
handleClick,
149+
});
150+
151+
updateLeaksCount();
152+
153+
console.log('Leaky card removed from DOM, but saved in memory:', id);
154+
});
155+
}
156+
157+
function createCleanCard() {
158+
counter++;
159+
160+
const id = counter;
161+
const node = document.createElement('div');
162+
const payload = createBigPayload(id);
163+
164+
node.className = 'card clean';
165+
node.textContent = `Clean card #${id}: I will disappear and can be cleaned by GC`;
166+
167+
function handleClick() {
168+
console.log('Clean payload:', payload[0]);
169+
}
170+
171+
node.addEventListener('click', handleClick);
172+
173+
app.append(node);
174+
175+
removeWithAnimation(node, () => {
176+
/**
177+
* Правильная очистка.
178+
*
179+
* 1. Удалили listener.
180+
* 2. Удалили DOM node.
181+
* 3. Не сохранили ссылку глобально.
182+
*/
183+
node.removeEventListener('click', handleClick);
184+
185+
console.log('Clean card removed from DOM:', id);
186+
});
187+
}
188+
189+
function createManyLeakyCards() {
190+
for (let i = 0; i < 20; i++) {
191+
setTimeout(createLeakyCard, i * 150);
192+
}
193+
}
194+
195+
function createManyCleanCards() {
196+
for (let i = 0; i < 20; i++) {
197+
setTimeout(createCleanCard, i * 150);
198+
}
199+
}
200+
201+
function clearLeaks() {
202+
/**
203+
* После этого старые leaky-объекты смогут быть очищены GC.
204+
*/
205+
window.__leaks.length = 0;
206+
207+
updateLeaksCount();
208+
209+
console.log('Leaks cleared');
210+
}
211+
</script>
212+
</body>
213+
</html>

0 commit comments

Comments
 (0)