Khái niệm
WeakMap (ES2015) là tập hợp key-value pairs trong đó:
- Key phải là Object (không phải primitive)
- Weak reference: Key không bị GC ngăn cản - nếu object key không còn reference khác, nó và value sẽ bị GC dọn dẹp
- Không iterable, không có
.size, không.clear(), khôngforEach - Chỉ có 4 method:
get, set, has, delete
So sánh Map vs WeakMap
| Đặc điểm | Map | WeakMap |
|---|---|---|
| Key type | Bất kỳ | Chỉ Object |
| Reference | Strong | Weak (GC friendly) |
| Iterable | ✅ | ❌ |
| size property | ✅ | ❌ |
| clear() | ✅ | ❌ |
| Use case | General purpose | Metadata, cache, private data |
Ví dụ: Private Data & Auto Cleanup
<!DOCTYPE html>
<html lang="vi">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WeakMap</title>
<style>
body { font-family: 'Segoe UI', sans-serif; padding: 20px; }
.result { font-size: 16px; font-weight: 500; color: #6a1b9a; margin: 15px 0; padding: 15px; background: #f3e5f5; border-radius: 4px; white-space: pre-wrap; }
button { padding: 10px 20px; background: #1976d2; color: white; border: none; border-radius: 4px; cursor: pointer; margin-right: 10px; }
</style>
</head>
<body>
<h1>WeakMap trong JavaScript</h1>
<div class="result" id="result"></div>
<button id="btnShow">HIỂN THỊ WEAKMAP</button>
<button id="btnRemove">XÓA THAM CHIẾU KEY</button>
<h3>Nhấn HIỂN THỊ để xem WeakMap, XÓA để test GC</h3>
<script>
const resultEle = document.getElementById('result');
const btnShow = document.getElementById('btnShow');
const btnRemove = document.getElementById('btnRemove');
// Object làm key
let personObj = { name: 'Rohan Sharma', age: 22, class: 9 };
const weakMap = new WeakMap();
weakMap.set(personObj, 'Rohan Object Data');
btnShow.addEventListener('click', () => {
if (personObj) {
resultEle.textContent = `personObj: ${weakMap.get(personObj)}`;
} else {
resultEle.textContent = 'personObj đã null - GC có thể đã dọn';
}
});
btnRemove.addEventListener('click', () => {
personObj = null; // Xóa reference duy nhất
resultEle.textContent += '\n✅ Đã set personObj = null';
resultEle.textContent += '\n🗑️ GC sẽ dọn personObj và entry trong WeakMap khi chạy';
// Force GC không thể từ JS, nhưng có thể test trong DevTools
});
</script>
</body>
</html>
Kết quả
Nhấn "HIỂN THỊ": personObj: Rohan Object Data
Nhấn "XÓA THAM CHIẾU": personObj = null → WeakMap entry tự động bị GC dọn sau đó.
Use Cases thực tế
1. Private Data cho Class (Trước #private fields)
const privateData = new WeakMap();
class Person {
constructor(name, ssn) {
privateData.set(this, { ssn, bankAccount: 'secret' });
}
getSSN() {
return privateData.get(this).ssn;
}
}
const p = new Person('John', '123-45-6789');
console.log(p.getSSN()); // 123-45-6789
// privateData không thể truy cập từ bên ngoài
2. Cache/Memoization cho Object
const cache = new WeakMap();
function expensiveComputation(obj) {
if (cache.has(obj)) return cache.get(obj);
// Heavy computation
const result = heavyWork(obj);
cache.set(obj, result);
return result;
}
// Khi obj không dùng nữa -> cache entry tự biến mất
3. DOM Element Metadata (Không rò rỉ bộ nhớ)
const elementData = new WeakMap();
function trackElement(el) {
elementData.set(el, {
created: Date.now(),
clicks: 0
});
}
function onClick(el) {
const data = elementData.get(el);
data.clicks++;
console.log(`Element clicked ${data.clicks} times`);
}
// Khi element bị remove khỏi DOM -> GC dọn cả elementData entry
4. Observer Pattern
const observers = new WeakMap();
function observe(target, callback) {
if (!observers.has(target)) observers.set(target, new Set());
observers.get(target).add(callback);
}
function notify(target, ...args) {
observers.get(target)?.forEach(cb => cb(...args));
}