How to use WeakMap for private data in JavaScript
WeakMap provides truly private data storage for objects without memory leaks, as keys are weakly referenced and garbage collected when no other references exist. As the creator of CoreUI with 26 years of JavaScript development experience, I’ve used WeakMap to implement private properties in components and services that handle sensitive data for millions of users.
The most secure approach uses WeakMap instances to store private state associated with public object instances.
Basic Private Data Pattern
const privateData = new WeakMap()
class User {
constructor(name, password) {
// Public property
this.name = name
// Private data stored in WeakMap
privateData.set(this, {
password: password,
loginCount: 0
})
}
login(password) {
const data = privateData.get(this)
if (data.password === password) {
data.loginCount++
return true
}
return false
}
getLoginCount() {
return privateData.get(this).loginCount
}
}
const user = new User('john', 'secret123')
console.log(user.name) // 'john'
console.log(user.password) // undefined (private)
console.log(user.login('secret123')) // true
console.log(user.getLoginCount()) // 1
Multiple Private Properties
const _password = new WeakMap()
const _email = new WeakMap()
const _createdAt = new WeakMap()
class Account {
constructor(username, email, password) {
this.username = username
_email.set(this, email)
_password.set(this, password)
_createdAt.set(this, new Date())
}
authenticate(password) {
return _password.get(this) === password
}
getEmail() {
return _email.get(this)
}
updateEmail(newEmail, password) {
if (this.authenticate(password)) {
_email.set(this, newEmail)
return true
}
return false
}
getAccountAge() {
const created = _createdAt.get(this)
return Math.floor((Date.now() - created) / (1000 * 60 * 60 * 24))
}
}
const account = new Account('john', '[email protected]', 'pass123')
console.log(account.getEmail()) // '[email protected]'
console.log(account.updateEmail('[email protected]', 'wrong')) // false
console.log(account.updateEmail('[email protected]', 'pass123')) // true
Private Methods Pattern
const privateMethods = new WeakMap()
class Calculator {
constructor() {
privateMethods.set(this, {
validateNumber(n) {
if (typeof n !== 'number' || isNaN(n)) {
throw new Error('Invalid number')
}
},
formatResult(result) {
return Math.round(result * 100) / 100
}
})
}
add(a, b) {
const methods = privateMethods.get(this)
methods.validateNumber(a)
methods.validateNumber(b)
return methods.formatResult(a + b)
}
divide(a, b) {
const methods = privateMethods.get(this)
methods.validateNumber(a)
methods.validateNumber(b)
if (b === 0) {
throw new Error('Division by zero')
}
return methods.formatResult(a / b)
}
}
const calc = new Calculator()
console.log(calc.add(10, 5)) // 15
console.log(calc.divide(10, 3)) // 3.33
Singleton with Private State
const instances = new WeakMap()
class Database {
constructor() {
// Singleton pattern
if (instances.has(Database)) {
return instances.get(Database)
}
instances.set(Database, this)
instances.set(this, {
connected: false,
queries: []
})
}
connect() {
const state = instances.get(this)
state.connected = true
console.log('Database connected')
}
query(sql) {
const state = instances.get(this)
if (!state.connected) {
throw new Error('Not connected')
}
state.queries.push({
sql,
timestamp: Date.now()
})
return { success: true }
}
getQueryCount() {
return instances.get(this).queries.length
}
}
const db1 = new Database()
const db2 = new Database()
console.log(db1 === db2) // true (singleton)
db1.connect()
db1.query('SELECT * FROM users')
console.log(db2.getQueryCount()) // 1
Event Emitter with Private Listeners
const listeners = new WeakMap()
class EventEmitter {
constructor() {
listeners.set(this, new Map())
}
on(event, callback) {
const eventListeners = listeners.get(this)
if (!eventListeners.has(event)) {
eventListeners.set(event, [])
}
eventListeners.get(event).push(callback)
}
emit(event, data) {
const eventListeners = listeners.get(this)
const callbacks = eventListeners.get(event)
if (callbacks) {
callbacks.forEach(callback => callback(data))
}
}
off(event, callback) {
const eventListeners = listeners.get(this)
const callbacks = eventListeners.get(event)
if (callbacks) {
const index = callbacks.indexOf(callback)
if (index > -1) {
callbacks.splice(index, 1)
}
}
}
getListenerCount(event) {
const eventListeners = listeners.get(this)
const callbacks = eventListeners.get(event)
return callbacks ? callbacks.length : 0
}
}
const emitter = new EventEmitter()
const handler = (data) => console.log('Received:', data)
emitter.on('message', handler)
emitter.emit('message', 'Hello')
console.log(emitter.getListenerCount('message')) // 1
React Component with Private State
const privateState = new WeakMap()
class SecureInput {
constructor(element) {
this.element = element
privateState.set(this, {
value: '',
masked: true,
history: []
})
this.element.addEventListener('input', (e) => {
this.updateValue(e.target.value)
})
}
updateValue(newValue) {
const state = privateState.get(this)
state.history.push(state.value)
state.value = newValue
this.render()
}
toggleMask() {
const state = privateState.get(this)
state.masked = !state.masked
this.render()
}
render() {
const state = privateState.get(this)
if (state.masked) {
this.element.value = '*'.repeat(state.value.length)
} else {
this.element.value = state.value
}
}
getValue() {
return privateState.get(this).value
}
undo() {
const state = privateState.get(this)
if (state.history.length > 0) {
state.value = state.history.pop()
this.render()
}
}
}
// Usage
const input = new SecureInput(document.getElementById('password'))
input.toggleMask()
console.log(input.getValue()) // Access real value
Best Practice Note
This is the same WeakMap pattern we use in CoreUI’s components for truly private data encapsulation. WeakMap provides memory-safe private storage because entries are garbage collected when the key object is no longer referenced. Always use WeakMap instead of closures or Symbols when you need private instance data that should be collected with the instance.
Related Articles
For related encapsulation patterns, check out how to use WeakSet in JavaScript and how to implement module pattern in JavaScript.



