Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> JavaScript

WeakMap trong JavaScript

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ông forEach
  • Chỉ có 4 method: get, set, has, delete

So sánh Map vs WeakMap

Đặc điểmMapWeakMap
Key typeBất kỳChỉ Object
ReferenceStrongWeak (GC friendly)
Iterable✅❌
size property✅❌
clear()✅❌
Use caseGeneral purposeMetadata, 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));
}